Пример #1
0
        public static string AttackSequence(Player attacker, Player victim, SkillViewModel skillBeingUsed, bool timestamp = true)
        {
            // Actual attack
            var(_, message) = AttackProcedures.Attack(attacker, victim, skillBeingUsed, timestamp);

            // record into statistics
            StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__SpellsCast, 1);

            if (AIStatics.IsABoss(victim.BotId))
            {
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__BossAllAttacks, 1);
            }

            if (victim.BotId == AIStatics.FemaleRatBotId || victim.BotId == AIStatics.MaleRatBotId)
            {
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__BossRatThiefAttacks, 1);
            }
            else if (victim.BotId == AIStatics.BimboBossBotId)
            {
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__BossLovebringerAttacks, 1);
            }
            else if (victim.BotId == AIStatics.DonnaBotId)
            {
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__BossDonnaAttacks, 1);
            }
            else if (victim.BotId == AIStatics.FaebossBotId)
            {
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__FaebossAttacks, 1);
            }
            else if (victim.BotId == AIStatics.MouseNerdBotId || victim.BotId == AIStatics.MouseBimboBotId)
            {
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__MouseSisterAttacks, 1);
            }
            else if (victim.BotId == AIStatics.MotorcycleGangLeaderBotId)
            {
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__MotorcycleGangAttacks, 1);
            }
            else if (AIStatics.IsAMiniboss(victim.BotId) && victim.BotId != AIStatics.MinibossPlushAngelId) // The plush just wants to make friends. No rewards for monsters.
            {
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__MinibossAttacks, 1);
            }

            // Trigger any counterattack
            AIProcedures.CheckAICounterattackRoutine(attacker, victim);

            return(message);
        }
Пример #2
0
        private static void PerformAnimateTransformation(Player victim, Player attacker, DbStaticForm targetForm, LogBox output)
        {
            var playerRepo = new EFPlayerRepository();
            var dbVictim   = playerRepo.Players.FirstOrDefault(p => p.Id == victim.Id);

            SkillProcedures.UpdateFormSpecificSkillsToPlayer(dbVictim, targetForm.Id);
            DomainRegistry.Repository.Execute(new ChangeForm
            {
                PlayerId     = dbVictim.Id,
                FormSourceId = targetForm.Id
            });

            // wipe out half of the target's mana
            dbVictim.Mana -= dbVictim.MaxMana / 2;
            if (dbVictim.Mana < 0)
            {
                dbVictim.Mana = 0;
            }

            // Remove any Self Restore entires.
            RemoveSelfRestore(dbVictim);

            var targetbuffs = ItemProcedures.GetPlayerBuffs(dbVictim);

            dbVictim = PlayerProcedures.ReadjustMaxes(dbVictim, targetbuffs);


            // take away some of the victim's XP based on the their level
            // target.XP += -2.5M * target.Level;

            playerRepo.SavePlayer(dbVictim);

            output.LocationLog += $"<br><b>{dbVictim.GetFullName()} was completely transformed into a {targetForm.FriendlyName} here.</b>";
            output.AttackerLog += $"<br><b>You fully transformed {dbVictim.GetFullName()} into a {targetForm.FriendlyName}</b>!";
            output.VictimLog   += $"<br><b>You have been fully transformed into a {targetForm.FriendlyName}!</b>";

            // Let the target know they are best friends with the angel plush.
            if (attacker.BotId == AIStatics.MinibossPlushAngelId)
            {
                output.VictimLog += $"<br><br><b>{attacker.GetFullName()}</b> was happy to make you into a new friend!<br>";
            }

            TFEnergyProcedures.DeleteAllPlayerTFEnergiesOfFormSourceId(dbVictim.Id, targetForm.Id);

            StatsProcedures.AddStat(dbVictim.MembershipId, StatsProcedures.Stat__TimesAnimateTFed, 1);
            StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__TimesAnimateTFing, 1);
        }
Пример #3
0
        public static void AddDiscoverableSpellStat(string membershipId, string stat)
        {
            var numDiscoverableSpells = SkillStatics.GetNumPlayerLearnableSkills();

            StatsProcedures.AddStat(membershipId, stat, 1, numDiscoverableSpells);
        }
        public static string ReturnToAnimate(Player player, bool dungeonPenalty)
        {
            IInanimateXPRepository inanimXpRepo = new EFInanimateXPRepository();
            IItemRepository        itemRepo     = new EFItemRepository();

            var inanimXP = inanimXpRepo.InanimateXPs.FirstOrDefault(i => i.OwnerId == player.Id);

            var currentGameTurn = PvPWorldStatProcedures.GetWorldTurnNumber();

            if (inanimXP == null)
            {
                inanimXP = new InanimateXP
                {
                    OwnerId = player.Id,
                    Amount  = 0,

                    // set the initial times struggled proportional to how high of a level the player is
                    TimesStruggled      = -6 * player.Level,
                    LastActionTimestamp = DateTime.UtcNow,
                    LastActionTurnstamp = currentGameTurn - 1,
                };
            }

            double strugglebonus = currentGameTurn - inanimXP.LastActionTurnstamp;

            if (strugglebonus > TurnTimesStatics.GetItemMaxTurnsBuildup())
            {
                strugglebonus = TurnTimesStatics.GetItemMaxTurnsBuildup();
            }

            if (strugglebonus < 0)
            {
                strugglebonus = 0;
            }

            if (PvPStatics.ChaosMode)
            {
                strugglebonus = 100;
            }

            // increment the player's attack count.  Also decrease their player XP some.
            IPlayerRepository playerRepo = new EFPlayerRepository();
            var dbPlayer = playerRepo.Players.FirstOrDefault(p => p.Id == player.Id);

            dbPlayer.TimesAttackingThisUpdate++;

            var strugglesMade = Convert.ToDouble(GetStruggleChance(player, dungeonPenalty));

            var rand = new Random();
            var roll = rand.NextDouble() * 100;

            var dbPlayerItem = DomainRegistry.Repository.FindSingle(new GetItemByFormerPlayer {
                PlayerId = player.Id
            });

            if (dbPlayerItem == null)
            {
                return("Cannot struggle - no player item");
            }

            if (dbPlayerItem.Owner != null)
            {
                var owner = PlayerProcedures.GetPlayer(dbPlayerItem.Owner.Id);
                dbPlayer.dbLocationName = owner.dbLocationName;
            }

            var itemPlus = ItemStatics.GetStaticItem(dbPlayerItem.ItemSource.Id);

            if (roll < strugglesMade)
            {
                // assert that the covenant the victim is in is not too full to accept them back in
                if (dbPlayer.Covenant > 0)
                {
                    var victimCov = CovenantProcedures.GetCovenantViewModel((int)dbPlayer.Covenant).dbCovenant;
                    if (victimCov != null && CovenantProcedures.GetPlayerCountInCovenant(victimCov, true) >= PvPStatics.Covenant_MaximumAnimatePlayerCount)
                    {
                        return("Although you had enough energy to break free from your body as a " + itemPlus.FriendlyName + " and restore your regular body, you were unfortunately not able to break free because there is no more room in your covenant for any more animate mages.");
                    }
                }


                // if the item has an owner, notify them via a message.
                if (dbPlayerItem.Owner != null)
                {
                    var message = player.FirstName + " " + player.LastName + ", your " + itemPlus.FriendlyName + ", successfully struggles against your magic and reverses their transformation.  You can no longer claim them as your property, not unless you manage to turn them back again...";
                    PlayerLogProcedures.AddPlayerLog(dbPlayerItem.Owner.Id, message, true);
                }

                // change the player's form and mobility
                DomainRegistry.Repository.Execute(new ChangeForm
                {
                    PlayerId     = dbPlayer.Id,
                    FormSourceId = dbPlayer.OriginalFormSourceId
                });

                dbPlayer.ActionPoints               = TurnTimesStatics.GetActionPointLimit();
                dbPlayer.ActionPoints_Refill        = TurnTimesStatics.GetActionPointReserveLimit();
                dbPlayer.CleansesMeditatesThisRound = PvPStatics.MaxCleansesMeditatesPerUpdate;
                dbPlayer.TimesAttackingThisUpdate   = PvPStatics.MaxAttacksPerUpdate;

                // don't let the player spawn in the dungeon as they will have Back On Your Feet
                // and may not be meet the level and game mode requirements
                if (dbPlayer.IsInDungeon())
                {
                    dbPlayer.dbLocationName = LocationsStatics.GetRandomLocationNotInDungeon();
                }

                dbPlayer        = PlayerProcedures.ReadjustMaxes(dbPlayer, ItemProcedures.GetPlayerBuffs(dbPlayer));
                dbPlayer.Health = dbPlayer.MaxHealth / 3;
                dbPlayer.Mana   = dbPlayer.MaxHealth / 3;
                playerRepo.SavePlayer(dbPlayer);

                // drop any runes embedded on the player-item, or return them to the former owner's inventory
                DomainRegistry.Repository.Execute(new UnbembedRunesOnItem {
                    ItemId = dbPlayerItem.Id
                });

                // delete the item or animal that this player had turned into
                itemRepo.DeleteItem(dbPlayerItem.Id);

                // delete the inanimate XP item
                inanimXpRepo.DeleteInanimateXP(inanimXP.Id);

                // give the player the recovery buff
                EffectProcedures.GivePerkToPlayer(PvPStatics.Effect_BackOnYourFeetSourceId, dbPlayer);

                var msg = "You have managed to break free from your form as " + itemPlus.FriendlyName + " and occupy an animate body once again!";

                if (PvPStatics.ChaosMode)
                {
                    msg += $" [CHAOS MODE:  Struggle value overriden to {strugglebonus:0}% per struggle.]";
                }

                PlayerLogProcedures.AddPlayerLog(dbPlayer.Id, msg, false);

                StatsProcedures.AddStat(dbPlayer.MembershipId, StatsProcedures.Stat__SuccessfulStruggles, 1);

                return(msg);
            }

            // failure to break free; increase time struggles
            else
            {
                // raise the probability of success for next time somewhat proportion to how many turns they missed
                inanimXP.TimesStruggled     += Convert.ToInt32(strugglebonus);
                inanimXP.LastActionTimestamp = DateTime.UtcNow;
                inanimXP.LastActionTurnstamp = currentGameTurn;
                inanimXpRepo.SaveInanimateXP(inanimXP);

                playerRepo.SavePlayer(dbPlayer);

                if (dbPlayerItem.Owner != null)
                {
                    var message = $"{player.FirstName} {player.LastName}, your {itemPlus.FriendlyName}, struggles but fails to return to an animate form.  [Recovery chance next struggle:  {(int)GetStruggleChance(player, dungeonPenalty)}%]";
                    PlayerLogProcedures.AddPlayerLog(dbPlayerItem.Owner.Id, message, true);
                }

                PlayerLogProcedures.AddPlayerLog(dbPlayer.Id, "You struggled to return to a human form.", false);

                return($"Unfortunately you are not able to struggle free from your form as {itemPlus.FriendlyName}.  Keep trying and you might succeed later... [Recovery chance next struggle:  {(int)GetStruggleChance(player, dungeonPenalty)}%]");
            }
        }
        public static string GiveInanimateXP(string membershipId, bool isWhitelist)
        {
            IInanimateXPRepository inanimXpRepo = new EFInanimateXPRepository();
            IItemRepository        itemRep      = new EFItemRepository();

            // get the current level of this player based on what item they are
            var me = PlayerProcedures.GetPlayerFromMembership(membershipId);
            var inanimateMeHack = DomainRegistry.Repository.FindSingle(new GetItemByFormerPlayer {
                PlayerId = me.Id
            });
            var inanimateMe = itemRep.Items.FirstOrDefault(i => i.Id == inanimateMeHack.Id); // TODO: Replace with proper Command

            var currentGameTurn = PvPWorldStatProcedures.GetWorldTurnNumber();

            decimal xpGain = 0;

            // get the number of inanimate accounts under this IP
            IPlayerRepository playerRepo  = new EFPlayerRepository();
            decimal           playerCount = playerRepo.Players.Count(p => p.IpAddress == me.IpAddress && (p.Mobility == PvPStatics.MobilityInanimate || p.Mobility == PvPStatics.MobilityPet) && p.BotId == AIStatics.ActivePlayerBotId);

            if (playerCount == 0 || isWhitelist)
            {
                playerCount = 1;
            }

            var xp = inanimXpRepo.InanimateXPs.FirstOrDefault(i => i.OwnerId == me.Id);

            if (xp == null)
            {
                xp = new InanimateXP
                {
                    OwnerId             = me.Id,
                    Amount              = xpGain / playerCount,
                    TimesStruggled      = -6 * me.Level,
                    LastActionTimestamp = DateTime.UtcNow,
                    LastActionTurnstamp = currentGameTurn - 1,
                };

                if (me.Mobility == PvPStatics.MobilityInanimate)
                {
                    StatsProcedures.AddStat(me.MembershipId, StatsProcedures.Stat__InanimateXPEarned, (float)xpGain);
                }
                else if (me.Mobility == PvPStatics.MobilityPet)
                {
                    StatsProcedures.AddStat(me.MembershipId, StatsProcedures.Stat__PetXPEarned, (float)xpGain);
                }
            }
            else
            {
                double turnsSinceLastAction = currentGameTurn - xp.LastActionTurnstamp;

                if (turnsSinceLastAction > TurnTimesStatics.GetItemMaxTurnsBuildup())
                {
                    turnsSinceLastAction = TurnTimesStatics.GetItemMaxTurnsBuildup();
                }

                if (turnsSinceLastAction < 0)
                {
                    turnsSinceLastAction = 0;
                }

                xpGain += Convert.ToDecimal(turnsSinceLastAction) * InanimateXPStatics.XPGainPerInanimateAction;
                xpGain  = xpGain / playerCount;

                if (me.Mobility == PvPStatics.MobilityInanimate)
                {
                    StatsProcedures.AddStat(me.MembershipId, StatsProcedures.Stat__InanimateXPEarned, (float)xpGain);
                }
                else if (me.Mobility == PvPStatics.MobilityPet)
                {
                    StatsProcedures.AddStat(me.MembershipId, StatsProcedures.Stat__PetXPEarned, (float)xpGain);
                }

                xp.Amount             += xpGain;
                xp.TimesStruggled     -= 2 * Convert.ToInt32(turnsSinceLastAction);
                xp.LastActionTimestamp = DateTime.UtcNow;
                xp.LastActionTurnstamp = currentGameTurn;
            }

            var resultMessage = "  ";

            if (xp.Amount >= Convert.ToDecimal(ItemProcedures.GetXPRequiredForItemPetLevelup(inanimateMe.Level)))
            {
                xp.Amount -= Convert.ToDecimal(ItemProcedures.GetXPRequiredForItemPetLevelup(inanimateMe.Level));
                inanimateMe.Level++;
                itemRep.SaveItem(inanimateMe);

                resultMessage += $"  You have gained {xpGain:0.#} xp.  <b>Congratulations, you have gained a level!  Your owner will be so proud...</b>";

                var wearerMessage = "<span style='color: darkgreen'>" + me.FirstName + " " + me.LastName + ", currently your " + ItemStatics.GetStaticItem(inanimateMe.ItemSourceId).FriendlyName + ", has gained a level!  Treat them kindly and they might keep helping you out...</span>";

                // now we need to change the owner's max health or mana based on this leveling
                if (inanimateMe.OwnerId > 0)
                {
                    PlayerLogProcedures.AddPlayerLog((int)inanimateMe.OwnerId, wearerMessage, true);
                    var inanimateMePlus = ItemProcedures.GetItemViewModel(inanimateMe.Id);

                    if (inanimateMePlus.Item.HealthBonusPercent != 0.0M || inanimateMePlus.Item.ManaBonusPercent != 0.0M)
                    {
                        var myowner = playerRepo.Players.FirstOrDefault(p => p.Id == inanimateMe.OwnerId);

                        var healthChange = PvPStatics.Item_LevelBonusModifier * inanimateMePlus.Item.HealthBonusPercent;
                        var manaChange   = PvPStatics.Item_LevelBonusModifier * inanimateMePlus.Item.ManaBonusPercent;

                        myowner.MaxHealth += healthChange;
                        myowner.MaxMana   += manaChange;

                        if (myowner.MaxHealth < 1)
                        {
                            myowner.MaxHealth = 1;
                        }

                        if (myowner.MaxMana < 1)
                        {
                            myowner.MaxMana = 1;
                        }

                        if (myowner.Health > myowner.MaxHealth)
                        {
                            myowner.Health = myowner.MaxHealth;
                        }

                        if (myowner.Mana > myowner.MaxMana)
                        {
                            myowner.Mana = myowner.MaxMana;
                        }

                        playerRepo.SavePlayer(myowner);
                    }
                }
            }
            else
            {
                resultMessage = $"  You have gained {xpGain:0.#} xp.  ({xp.Amount:0.#}/{ItemProcedures.GetXPRequiredForItemPetLevelup(inanimateMe.Level):0.#} to next level).";
            }

            inanimXpRepo.SaveInanimateXP(xp);

            // lock the player into their fate if their inanimate XP gets too high
            if (xp.TimesStruggled <= TurnTimesStatics.GetStruggleXPBeforeItemPermanentLock() * .5 && xp.TimesStruggled > TurnTimesStatics.GetStruggleXPBeforeItemPermanentLock() && !inanimateMe.IsPermanent)
            {
                resultMessage += "  Careful, if you keep doing this you may find yourself stuck in your current form forever...";
            }

            if (xp.TimesStruggled <= TurnTimesStatics.GetStruggleXPBeforeItemPermanentLock() && !inanimateMe.IsPermanent)
            {
                inanimateMe.IsPermanent = true;
                itemRep.SaveItem(inanimateMe);
                DomainRegistry.Repository.Execute(new RemoveSoulbindingOnPlayerItems {
                    PlayerId = me.Id
                });
                DomainRegistry.Repository.Execute(new DropAllItems {
                    PlayerId = me.Id, IgnoreRunes = false
                });

                var formRepo = new EFDbStaticFormRepository();
                var form     = formRepo.DbStaticForms.FirstOrDefault(f => f.Id == me.FormSourceId);

                if (inanimateMe.OwnerId != null && form != null)
                {
                    PlayerLogProcedures.AddPlayerLog(inanimateMe.OwnerId.Value, $"{me.GetFullName()} has locked and is now unable to escape their form as your {form.FriendlyName}!", true);
                }

                PlayerLogProcedures.AddPlayerLog(me.Id, $"You have locked in your current form as a {form.FriendlyName}!", false);
                resultMessage += "  <b>You find the last of your old human self slip away as you permanently embrace your new form.</b>";
            }

            return(resultMessage);
        }
Пример #6
0
        private static void PerformInanimateTransformation(Player victim, Player attacker, int skillSourceId, DbStaticForm targetForm, LogBox output)
        {
            SkillProcedures.UpdateFormSpecificSkillsToPlayer(victim, targetForm.Id);
            DomainRegistry.Repository.Execute(new ChangeForm
            {
                PlayerId     = victim.Id,
                FormSourceId = targetForm.Id
            });

            if (targetForm.MobilityType == PvPStatics.MobilityInanimate && victim.BotId != AIStatics.MinibossPlushAngelId) //No reward for monsters that hurt an innocent little plush friend. :(
            {
                StatsProcedures.AddStat(victim.MembershipId, StatsProcedures.Stat__TimesInanimateTFed, 1);
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__TimesInanimateTFing, 1);
            }
            else if (targetForm.MobilityType == PvPStatics.MobilityPet && victim.BotId != AIStatics.MinibossPlushAngelId) //No reward for monsters that hurt an innocent little plush friend. :(
            {
                StatsProcedures.AddStat(victim.MembershipId, StatsProcedures.Stat__TimesAnimalTFed, 1);
                StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__TimesAnimalTFing, 1);
            }

            if (targetForm.MobilityType == PvPStatics.MobilityPet || targetForm.MobilityType == PvPStatics.MobilityInanimate)
            {
                if (victim.BotId == AIStatics.PsychopathBotId)
                {
                    StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__PsychopathsDefeated, 1);
                }

                if (victim.BotId == AIStatics.ActivePlayerBotId && attacker.GameMode == (int)GameModeStatics.GameModes.PvP && victim.GameMode == (int)GameModeStatics.GameModes.PvP)
                {
                    StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__PvPPlayerNumberTakedowns, 1);
                    StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__PvPPlayerLevelTakedowns, victim.Level);
                }
            }

            // extra log stuff for turning into item
            var extra = ItemProcedures.PlayerBecomesItem(victim, targetForm, attacker);

            output.AttackerLog += extra.AttackerLog;
            output.VictimLog   += extra.VictimLog;
            output.LocationLog += extra.LocationLog;

            // give some of the victim's money to the attacker, the amount depending on what mode the victim is in
            var moneygain = victim.Money * .35M;

            PlayerProcedures.GiveMoneyToPlayer(attacker, moneygain);
            PlayerProcedures.GiveMoneyToPlayer(victim, -moneygain / 2);

            var levelDifference = attacker.Level - victim.Level;

            // only give the lump sum XP if the victim is not in the same covenant
            if (attacker.Covenant == null || attacker.Covenant != victim.Covenant)
            {
                var xpGain = 100 - (PvPStatics.XP__EndgameTFCompletionLevelBase * levelDifference);

                if (xpGain < 50)
                {
                    xpGain = 50;
                }
                else if (xpGain > 200)
                {
                    xpGain = 200;
                }

                // give the attacker a nice lump sum for having completed the transformation
                output.AttackerLog += $"  <br>For having sealed your opponent into their new form, you gain an extra <b>{xpGain}</b> XP.";
                output.AttackerLog += PlayerProcedures.GiveXP(attacker, xpGain);
            }

            // exclude PvP score for bots
            if (victim.BotId == AIStatics.ActivePlayerBotId)
            {
                var score = PlayerProcedures.GetPvPScoreFromWin(attacker, victim);

                if (score > 0)
                {
                    output.AttackerLog += PlayerProcedures.GivePlayerPvPScore(attacker, victim, score);
                    output.VictimLog   += PlayerProcedures.RemovePlayerPvPScore(victim, attacker, score);

                    StatsProcedures.AddStat(attacker.MembershipId, StatsProcedures.Stat__DungeonPointsStolen, (float)score);
                }
                else
                {
                    output.AttackerLog += $"  {victim.GetFullName()} unfortunately did not have any dungeon points for you to steal for yourself.";
                }
            }

            // Call out a player for being the monster they are when they defeat the plush angel.
            if (victim.BotId == AIStatics.MinibossPlushAngelId)
            {
                output.AttackerLog += "<br><br>Why did you do that to the poor plush? They just wanted to be a friend!<br>";
                output.LocationLog += $"<br><b>{attacker.GetFullName()}</b> went and bullied <b>{victim.GetFullName()}</b>, like some <b>monster</b>. The angelic plush left some flowers to the 'victor', in hope they would forgive it despite doing no wrong.";

                // Give the dummy a bit of madness for being a bully.
                EffectProcedures.GivePerkToPlayer(198, attacker);
            }

            // Heals the victorious player provided that the target was eligible
            if (attacker.BotId == AIStatics.ActivePlayerBotId)
            {
                // Provide no healing if the victim shared a coven with the attacker
                if (attacker.Covenant != null && attacker.Covenant == victim.Covenant)
                {
                    output.AttackerLog += "  <br>There is no glory to be had in this victory, your willpower & mana are not restored.";
                }
                else
                {
                    // Figure out the modifier to be used
                    double modifier = (levelDifference * 5) / 100;
                    // Cap the modifier to prevent too much / too little healing.
                    if (modifier > 0.3)
                    {
                        modifier = 0.3;
                    }
                    if (modifier < -0.55)
                    {
                        modifier = -0.55;
                    }
                    decimal healingPercent = (decimal)(0.6 + modifier);

                    if (victim.BotId != AIStatics.ActivePlayerBotId)
                    {
                        // The victim is not a player, provide half of the healing.
                        healingPercent /= 2;
                    }

                    // Heal the attacker and restore their Mana
                    var healingTotal      = attacker.MaxHealth * healingPercent;
                    var manaRestoredTotal = attacker.MaxMana * healingPercent;
                    PlayerProcedures.ChangePlayerActionMana(0, healingTotal, manaRestoredTotal, attacker.Id, false);

                    // Remove any Self Restore entires.
                    RemoveSelfRestore(victim);

                    output.AttackerLog += $"<br />Invigorated by your victory and fuelled by the scattered essence that was once your foe, you are healed for {healingTotal:#} willpower and {manaRestoredTotal:#} mana.";
                }
            }

            output.AttackerLog += $"  You collect {Math.Round(moneygain, 0)} Arpeyjis your victim dropped during the transformation.";

            // create inanimate XP for the victim
            InanimateXPProcedures.GetStruggleChance(victim, false);

            // if this victim is a bot, clear out some old stuff that is not needed anymore
            if (victim.BotId < AIStatics.ActivePlayerBotId)
            {
                AIDirectiveProcedures.DeleteAIDirectiveByPlayerId(victim.Id);
                PlayerLogProcedures.ClearPlayerLog(victim.Id);
            }

            TFEnergyProcedures.DeleteAllPlayerTFEnergiesOfFormSourceId(victim.Id, targetForm.Id);

            // if the attacker is a psycho, have them change to a new spell and equip whatever they just earned
            if (attacker.BotId == AIStatics.PsychopathBotId)
            {
                SkillProcedures.DeletePlayerSkill(attacker, skillSourceId);

                if (targetForm.MobilityType == PvPStatics.MobilityInanimate || targetForm.MobilityType == PvPStatics.MobilityPet)
                {
                    if (attacker.MembershipId.IsNullOrEmpty())
                    {
                        // give this bot a random replacement inanimate/pet skill
                        var eligibleSkills = SkillStatics.GetLearnablePsychopathSkills().ToList();
                        var rand           = new Random();
                        var skillToLearn   = eligibleSkills.ElementAt(rand.Next(eligibleSkills.Count));
                        SkillProcedures.GiveSkillToPlayer(attacker.Id, skillToLearn.Id);
                    }
                    else
                    {
                        // Bot is being controlled by a player - re-add the original skill so only the ordering of skills changes
                        SkillProcedures.GiveSkillToPlayer(attacker.Id, skillSourceId);
                    }
                }
            }
        }