Ejemplo n.º 1
0
        /// <summary>
        /// Gives GP to a player for a given guild.
        /// If the baseAmount is less than 1, nothing will happen.
        /// If the baseAmount is greater than 1000, the baseAmount will be set to 1000.
        /// If the player ranks up, a message will be sent to him/her and an OnPlayerGuildRankUp event will be published.
        /// </summary>
        /// <param name="player">The player to give GP.</param>
        /// <param name="guild">The guild this GP will apply to.</param>
        /// <param name="baseAmount">The baseAmount of GP to grant.</param>
        public static void GiveGuildPoints(NWPlayer player, GuildType guild, int baseAmount)
        {
            if (baseAmount <= 0)
            {
                return;
            }

            // Clamp max GP baseAmount
            if (baseAmount > 1000)
            {
                baseAmount = 1000;
            }

            // Grant a bonus based on the player's guild relations perk rank. Always offset by 1 so we don't end up with multiplication by zero.
            int perkBonus = PerkService.GetCreaturePerkLevel(player, PerkType.GuildRelations) + 1;

            baseAmount *= perkBonus;

            var dbGuild = DataService.Get <Guild>((int)guild);
            var pcGP    = DataService.Single <PCGuildPoint>(x => x.GuildID == (int)guild && x.PlayerID == player.GlobalID);

            pcGP.Points += baseAmount;

            // Clamp player GP to the highest rank.
            int maxRank = RankProgression.Keys.Max();
            int maxGP   = RankProgression[maxRank];

            if (pcGP.Points >= maxGP)
            {
                pcGP.Points = maxGP - 1;
            }

            // Notify player how much GP they earned.
            player.SendMessage("You earned " + baseAmount + " " + dbGuild.Name + " guild points.");

            // Are we able to rank up?
            bool didRankUp = false;

            if (pcGP.Rank < maxRank)
            {
                // Is it time for a rank up?
                int nextRank = RankProgression[pcGP.Rank];
                if (pcGP.Points >= nextRank)
                {
                    // Let's do a rank up.
                    pcGP.Rank++;
                    player.SendMessage(ColorTokenService.Green("You've reached rank " + pcGP.Rank + " in the " + dbGuild.Name + "!"));
                    didRankUp = true;
                }
            }

            // Submit changes to the DB/cache.
            DataService.SubmitDataChange(pcGP, DatabaseActionType.Update);

            // If the player ranked up, publish an event saying so.
            if (didRankUp)
            {
                MessageHub.Instance.Publish(new OnPlayerGuildRankUp(player.GlobalID, pcGP.Rank));
            }
        }
Ejemplo n.º 2
0
        public static void RunItemRepair(NWPlayer oPC, NWItem item, float amount, float maxReductionAmount)
        {
            // Prevent repairing for less than 0.01
            if (amount < 0.01f)
            {
                return;
            }

            float maxDurability = GetMaxDurability(item) - maxReductionAmount;
            float durability    = GetDurability(item) + amount;

            if (maxDurability < 0.01f)
            {
                maxDurability = 0.01f;
            }
            if (durability > maxDurability)
            {
                durability = maxDurability;
            }

            SetMaxDurability(item, maxDurability);
            SetDurability(item, durability);
            string durMessage = FormatDurability(durability) + " / " + FormatDurability(maxDurability);

            oPC.SendMessage(ColorTokenService.Green("You repaired your " + item.Name + ". (" + durMessage + ")"));
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Gives GP to a player for a given guild.
        /// If the baseAmount is less than 1, nothing will happen.
        /// If the baseAmount is greater than 1000, the baseAmount will be set to 1000.
        /// If the player ranks up, a message will be sent to him/her and an OnPlayerGuildRankUp event will be published.
        /// </summary>
        /// <param name="player">The player to give GP.</param>
        /// <param name="guild">The guild this GP will apply to.</param>
        /// <param name="baseAmount">The baseAmount of GP to grant.</param>
        public static void GiveGuildPoints(NWPlayer player, GuildType guild, int baseAmount)
        {
            if (baseAmount <= 0)
            {
                return;
            }

            // Clamp max GP baseAmount
            if (baseAmount > 1000)
            {
                baseAmount = 1000;
            }

            var dbGuild = DataService.Guild.GetByID((int)guild);
            var pcGP    = DataService.PCGuildPoint.GetByPlayerIDAndGuildID(player.GlobalID, (int)guild);

            pcGP.Points += baseAmount;

            // Clamp player GP to the highest rank.
            int maxRank = RankProgression.Keys.Max();
            int maxGP   = RankProgression[maxRank];

            if (pcGP.Points >= maxGP)
            {
                pcGP.Points = maxGP - 1;
            }

            // Notify player how much GP they earned.
            player.SendMessage("You earned " + baseAmount + " " + dbGuild.Name + " guild points.");

            // Are we able to rank up?
            bool didRankUp = false;

            if (pcGP.Rank < maxRank)
            {
                // Is it time for a rank up?
                int nextRank = RankProgression[pcGP.Rank];
                if (pcGP.Points >= nextRank)
                {
                    // Let's do a rank up.
                    pcGP.Rank++;
                    player.SendMessage(ColorTokenService.Green("You've reached rank " + pcGP.Rank + " in the " + dbGuild.Name + "!"));
                    didRankUp = true;
                }
            }

            // Submit changes to the DB/cache.
            DataService.SubmitDataChange(pcGP, DatabaseActionType.Update);

            // If the player ranked up, publish an event saying so.
            if (didRankUp)
            {
                MessageHub.Instance.Publish(new OnPlayerGuildRankUp(player.GlobalID, pcGP.Rank));
            }
        }
Ejemplo n.º 4
0
        private static void ShowMOTD()
        {
            NWPlayer            player = GetEnteringObject();
            ServerConfiguration config = DataService.ServerConfiguration.Get();
            string message             = ColorTokenService.Green("Welcome to " + config.ServerName + "!\n\nMOTD: ") + ColorTokenService.White(config.MessageOfTheDay);

            DelayCommand(6.5f, () =>
            {
                player.SendMessage(message);
            });
        }
Ejemplo n.º 5
0
        public static bool OnModuleExamine(NWPlayer examiner, NWObject target)
        {
            string backupDescription = target.GetLocalString("BACKUP_DESCRIPTION");

            if (!string.IsNullOrWhiteSpace(backupDescription))
            {
                target.UnidentifiedDescription = backupDescription;
            }
            if (!examiner.IsDM || !target.IsPlayer || target.IsDM)
            {
                return(false);
            }

            backupDescription = target.IdentifiedDescription;
            target.SetLocalString("BACKUP_DESCRIPTION", backupDescription);
            Player playerEntity    = DataService.Player.GetByID(target.GlobalID);
            NWArea area            = NWModule.Get().Areas.Single(x => x.Resref == playerEntity.RespawnAreaResref);
            string respawnAreaName = area.Name;

            StringBuilder description =
                new StringBuilder(
                    ColorTokenService.Green("ID: ") + target.GlobalID + "\n" +
                    ColorTokenService.Green("Character Name: ") + target.Name + "\n" +
                    ColorTokenService.Green("Respawn Area: ") + respawnAreaName + "\n" +
                    ColorTokenService.Green("Skill Points: ") + playerEntity.TotalSPAcquired + " (Unallocated: " + playerEntity.UnallocatedSP + ")" + "\n" +
                    ColorTokenService.Green("FP: ") + playerEntity.CurrentFP + " / " + playerEntity.MaxFP + "\n" +
                    ColorTokenService.Green("Skill Levels: ") + "\n\n");

            List <PCSkill> pcSkills = SkillService.GetAllPCSkills(target.Object);

            foreach (PCSkill pcSkill in pcSkills)
            {
                Skill skill = SkillService.GetSkill(pcSkill.SkillID);
                description.Append(skill.Name).Append(" rank ").Append(pcSkill.Rank).AppendLine();
            }

            description.Append("\n\n").Append(ColorTokenService.Green("Perks: ")).Append("\n\n");

            var pcPerks = DataService.PCPerk.GetAllByPlayerID(target.GlobalID);

            foreach (PCPerk pcPerk in pcPerks)
            {
                var perk = DataService.Perk.GetByID(pcPerk.PerkID);
                description.Append(perk.Name).Append(" Lvl. ").Append(pcPerk.PerkLevel).AppendLine();
            }

            description.Append("\n\n").Append(ColorTokenService.Green("Description: \n\n")).Append(backupDescription).AppendLine();
            target.UnidentifiedDescription = description.ToString();
            _.DelayCommand(0.1f, () => { _.SetDescription(target, backupDescription, false); });
            return(true);
        }
Ejemplo n.º 6
0
        private static void OnCreatureDeath()
        {
            NWCreature creature = Object.OBJECT_SELF;

            int npcGroupID = creature.GetLocalInt("NPC_GROUP");

            if (npcGroupID <= 0)
            {
                return;
            }

            NWObject oKiller = _.GetLastKiller();

            if (!oKiller.IsPlayer)
            {
                return;
            }

            string areaResref = creature.Area.Resref;

            List <KeyValuePair <NWPlayer, int> > playersToAdvance = new List <KeyValuePair <NWPlayer, int> >();
            NWPlayer oPC = _.GetFirstFactionMember(oKiller);

            while (oPC.IsValid)
            {
                if (areaResref != oPC.Area.Resref)
                {
                    oPC = _.GetNextFactionMember(oKiller);
                    continue;
                }

                if (_.GetDistanceBetween(creature, oPC) <= 0.0f || _.GetDistanceBetween(creature, oPC) > 20.0f)
                {
                    oPC = _.GetNextFactionMember(oKiller);
                    continue;
                }

                var playerID    = oPC.GlobalID;
                var killTargets = DataService.Where <PCQuestKillTargetProgress>(x => x.PlayerID == playerID && x.NPCGroupID == npcGroupID).ToList();

                foreach (var kt in killTargets)
                {
                    var questStatus = DataService.Get <PCQuestStatus>(kt.PCQuestStatusID);
                    var quest       = DataService.Get <Quest>(questStatus.QuestID);
                    var npcGroup    = DataService.Get <NPCGroup>(kt.NPCGroupID);

                    kt.RemainingToKill--;
                    string             targetGroupName = npcGroup.Name;
                    string             updateMessage   = "[" + quest.Name + "] " + targetGroupName + " remaining: " + kt.RemainingToKill;
                    DatabaseActionType action          = DatabaseActionType.Update;

                    if (kt.RemainingToKill <= 0)
                    {
                        updateMessage += " " + ColorTokenService.Green(" {COMPLETE}");
                        playersToAdvance.Add(new KeyValuePair <NWPlayer, int>(oPC, quest.ID));
                        action = DatabaseActionType.Delete;
                    }

                    DataService.SubmitDataChange(kt, action);

                    var pc = oPC;
                    _.DelayCommand(1.0f, () =>
                    {
                        pc.SendMessage(updateMessage);
                    });

                    string ruleName = quest.OnKillTargetRule;
                    if (!string.IsNullOrWhiteSpace(ruleName))
                    {
                        var pcCopy = oPC;
                        var rule   = GetQuestRule(ruleName);

                        string[] args = null;
                        if (!string.IsNullOrWhiteSpace(quest.OnKillTargetArgs))
                        {
                            args = quest.OnKillTargetArgs.Split(',');
                        }
                        rule.Run(pcCopy, creature, quest.ID, args);
                    }
                }

                oPC = _.GetNextFactionMember(oKiller);
            }

            foreach (var player in playersToAdvance)
            {
                AdvanceQuestState(player.Key, null, player.Value);
            }
        }
Ejemplo n.º 7
0
        public static string BuildBlueprintHeader(NWPlayer player, bool showAddedComponentList)
        {
            var model              = GetPlayerCraftingData(player);
            var bp                 = model.Blueprint;
            int playerEL           = CalculatePCEffectiveLevel(player, model.PlayerSkillRank, (SkillType)bp.SkillID);
            var baseStructure      = bp.BaseStructureID == null ? null : DataService.BaseStructure.GetByID(Convert.ToInt32(bp.BaseStructureID));
            var mainComponent      = DataService.ComponentType.GetByID(bp.MainComponentTypeID);
            var secondaryComponent = DataService.ComponentType.GetByID(bp.SecondaryComponentTypeID);
            var tertiaryComponent  = DataService.ComponentType.GetByID(bp.TertiaryComponentTypeID);

            string header = ColorTokenService.Green("Blueprint: ") + bp.Quantity + "x " + bp.ItemName + "\n";

            header += ColorTokenService.Green("Level: ") + (model.AdjustedLevel < 0 ? 0 : model.AdjustedLevel) + " (Base: " + (bp.BaseLevel < 0 ? 0 : bp.BaseLevel) + ")\n";
            header += ColorTokenService.Green("Difficulty: ") + CalculateDifficultyDescription(playerEL, model.AdjustedLevel) + "\n";

            if (baseStructure != null)
            {
                header += ColorTokenService.Green("Raises Atmosphere: ");
                if (baseStructure.HasAtmosphere)
                {
                    header += ColorTokenService.Green("Yes");
                }
                else
                {
                    header += ColorTokenService.Red("No");
                }

                header += "\n";
            }

            header += ColorTokenService.Green("Required Components (Required/Maximum): ") + "\n\n";

            string mainCounts = " (" + (model.MainMinimum > 0 ? Convert.ToString(model.MainMinimum) : "Optional") + "/" + model.MainMaximum + ")";

            header += ColorTokenService.Green("Main: ") + mainComponent.Name + mainCounts + "\n";

            if (bp.SecondaryMinimum > 0 && bp.SecondaryComponentTypeID > 0)
            {
                string secondaryCounts = " (" + (model.SecondaryMinimum > 0 ? Convert.ToString(model.SecondaryMinimum) : "Optional") + "/" + model.SecondaryMaximum + ")";
                header += ColorTokenService.Green("Secondary: ") + secondaryComponent.Name + secondaryCounts + "\n";
            }
            if (bp.TertiaryMinimum > 0 && bp.TertiaryComponentTypeID > 0)
            {
                string tertiaryCounts = " (" + (model.TertiaryMinimum > 0 ? Convert.ToString(model.TertiaryMinimum) : "Optional") + "/" + model.TertiaryMaximum + ")";
                header += ColorTokenService.Green("Tertiary: ") + tertiaryComponent.Name + tertiaryCounts + "\n";
            }
            if (bp.EnhancementSlots > 0)
            {
                int nSlots = bp.EnhancementSlots;
                if (model.IsInitialized)
                {
                    // We have the player's stats, so tell them how many they can actually add.
                    if (model.PlayerPerkLevel / 2 < nSlots)
                    {
                        nSlots = model.PlayerPerkLevel / 2;
                    }
                }

                string enhancementSlots = " (0/" + Convert.ToString(nSlots) + ")";
                header += ColorTokenService.Green("Enhancement slots: ") + enhancementSlots + "\n";
            }

            if (showAddedComponentList)
            {
                header += "\n" + ColorTokenService.Green("Your components:") + "\n\n";
                if (!model.HasPlayerComponents)
                {
                    header += "No components selected yet!";
                }
                else
                {
                    foreach (var item in model.MainComponents)
                    {
                        header += item.Name + "\n";
                    }
                    foreach (var item in model.SecondaryComponents)
                    {
                        header += item.Name + "\n";
                    }
                    foreach (var item in model.TertiaryComponents)
                    {
                        header += item.Name + "\n";
                    }
                    foreach (var item in model.EnhancementComponents)
                    {
                        header += item.Name + "\n";
                    }
                }
            }

            return(header);
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Updates a player's quest status if the creature is part of an ongoing quest.
        /// Progresses the player to the next state if all requirements are met.
        /// </summary>
        private static void OnCreatureDeath()
        {
            NWCreature creature = _.OBJECT_SELF;

            int npcGroupID = creature.GetLocalInt("NPC_GROUP");

            if (npcGroupID <= 0)
            {
                return;
            }

            NWObject oKiller = GetLastKiller();

            if (!oKiller.IsPlayer)
            {
                return;
            }

            string areaResref = creature.Area.Resref;

            List <KeyValuePair <NWPlayer, int> > playersToAdvance = new List <KeyValuePair <NWPlayer, int> >();
            NWPlayer oPC = GetFirstFactionMember(oKiller);

            while (oPC.IsValid)
            {
                if (areaResref != oPC.Area.Resref)
                {
                    oPC = GetNextFactionMember(oKiller);
                    continue;
                }

                if (GetDistanceBetween(creature, oPC) <= 0.0f || GetDistanceBetween(creature, oPC) > 40.0f)
                {
                    oPC = GetNextFactionMember(oKiller);
                    continue;
                }

                var playerID    = oPC.GlobalID;
                var killTargets = DataService.PCQuestKillTargetProgress.GetAllByPlayerIDAndNPCGroupID(playerID, npcGroupID).ToList();

                foreach (var kt in killTargets)
                {
                    var questStatus = DataService.PCQuestStatus.GetByID(kt.PCQuestStatusID);
                    var quest       = GetQuestByID(questStatus.QuestID);
                    var npcGroup    = DataService.NPCGroup.GetByID(kt.NPCGroupID);

                    kt.RemainingToKill--;
                    string             targetGroupName = npcGroup.Name;
                    string             updateMessage   = "[" + quest.Name + "] " + targetGroupName + " remaining: " + kt.RemainingToKill;
                    DatabaseActionType action          = DatabaseActionType.Update;

                    if (kt.RemainingToKill <= 0)
                    {
                        updateMessage += " " + ColorTokenService.Green(" {COMPLETE}");
                        playersToAdvance.Add(new KeyValuePair <NWPlayer, int>(oPC, quest.QuestID));
                        action = DatabaseActionType.Delete;
                    }

                    DataService.SubmitDataChange(kt, action);

                    var pc = oPC;
                    DelayCommand(1.0f, () =>
                    {
                        pc.SendMessage(updateMessage);
                    });
                }

                oPC = GetNextFactionMember(oKiller);
            }

            foreach (var player in playersToAdvance)
            {
                var quest = GetQuestByID(player.Value);
                quest.Advance(player.Key, creature);
            }
        }
Ejemplo n.º 9
0
 public static string PrismaticString()
 {
     return(ColorTokenService.Red("p") + ColorTokenService.Orange("r") + ColorTokenService.Yellow("i") + ColorTokenService.Green("s") + ColorTokenService.Blue("m") +
            ColorTokenService.LightPurple("a") + ColorTokenService.Purple("t") + ColorTokenService.White("i") + ColorTokenService.Black("c"));
 }
Ejemplo n.º 10
0
        public static void DoPerkUpgrade(NWPlayer oPC, int perkID, bool freeUpgrade = false)
        {
            var perk       = DataService.Single <Data.Entity.Perk>(x => x.ID == perkID);
            var perkLevels = DataService.Where <PerkLevel>(x => x.PerkID == perkID);
            var pcPerk     = DataService.SingleOrDefault <PCPerk>(x => x.PlayerID == oPC.GlobalID && x.PerkID == perkID);
            var player     = DataService.Single <Player>(x => x.ID == oPC.GlobalID);

            if (freeUpgrade || CanPerkBeUpgraded(oPC, perkID))
            {
                DatabaseActionType action = DatabaseActionType.Update;
                if (pcPerk == null)
                {
                    pcPerk = new PCPerk();
                    DateTime dt = DateTime.UtcNow;
                    pcPerk.AcquiredDate = dt;
                    pcPerk.PerkID       = perk.ID;
                    pcPerk.PlayerID     = oPC.GlobalID;
                    pcPerk.PerkLevel    = 0;

                    action = DatabaseActionType.Insert;
                }

                PerkLevel nextPerkLevel = FindPerkLevel(perkLevels, pcPerk.PerkLevel + 1);
                if (nextPerkLevel == null)
                {
                    return;
                }

                pcPerk.PerkLevel++;
                DataService.SubmitDataChange(pcPerk, action);

                if (!freeUpgrade)
                {
                    player.UnallocatedSP -= nextPerkLevel.Price;
                    DataService.SubmitDataChange(player, DatabaseActionType.Update);
                }

                // Look for any perk levels to grant.
                var perkFeatsToGrant = DataService.Where <PerkFeat>(x => x.PerkID == perkID && x.PerkLevelUnlocked == pcPerk.PerkLevel);

                // If at least one feat ID is assigned, add the feat(s) to the player if it doesn't exist yet.
                if (perkFeatsToGrant.Count > 0)
                {
                    foreach (var perkFeat in perkFeatsToGrant)
                    {
                        if (_.GetHasFeat(perkFeat.FeatID, oPC.Object) == TRUE)
                        {
                            continue;
                        }

                        NWNXCreature.AddFeatByLevel(oPC, perkFeat.FeatID, 1);

                        var qbs = NWNXPlayerQuickBarSlot.UseFeat(perkFeat.FeatID);

                        // Try to add the new feat to the player's hotbar.
                        if (NWNXPlayer.GetQuickBarSlot(oPC, 0).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 0, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 1).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 1, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 2).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 2, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 3).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 3, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 4).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 4, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 5).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 5, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 6).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 6, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 7).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 7, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 8).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 8, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 9).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 9, qbs);
                        }
                        else if (NWNXPlayer.GetQuickBarSlot(oPC, 10).ObjectType == QuickBarSlotType.Empty)
                        {
                            NWNXPlayer.SetQuickBarSlot(oPC, 10, qbs);
                        }
                    }
                }

                oPC.SendMessage(ColorTokenService.Green("Perk Purchased: " + perk.Name + " (Lvl. " + pcPerk.PerkLevel + ")"));

                var handler = GetPerkHandler(perkID);
                handler.OnPurchased(oPC, pcPerk.PerkLevel);
                MessageHub.Instance.Publish(new PerkUpgradedMessage(oPC, perkID));
            }
            else
            {
                oPC.FloatingText(ColorTokenService.Red("You cannot purchase the perk at this time."));
            }
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Performs a perk purchase for a player. This handles deducting SP, inserting perk records,
        /// and adjusting hotbar slots as necessary.
        /// </summary>
        /// <param name="oPC">The player receiving the perk upgrade.</param>
        /// <param name="perkID">The ID number of the perk.</param>
        /// <param name="freeUpgrade">If true, no SP will be deducted. Otherwise, SP will be deducted from player.</param>
        public static void DoPerkUpgrade(NWPlayer oPC, int perkID, bool freeUpgrade = false)
        {
            var perk       = DataService.Perk.GetByID(perkID);
            var perkLevels = DataService.PerkLevel.GetAllByPerkID(perkID);
            var pcPerk     = DataService.PCPerk.GetByPlayerAndPerkIDOrDefault(oPC.GlobalID, perkID);
            var player     = DataService.Player.GetByID(oPC.GlobalID);

            if (freeUpgrade || CanPerkBeUpgraded(oPC, perkID))
            {
                DatabaseActionType action = DatabaseActionType.Update;
                if (pcPerk == null)
                {
                    pcPerk = new PCPerk();
                    DateTime dt = DateTime.UtcNow;
                    pcPerk.AcquiredDate = dt;
                    pcPerk.PerkID       = perk.ID;
                    pcPerk.PlayerID     = oPC.GlobalID;
                    pcPerk.PerkLevel    = 0;

                    action = DatabaseActionType.Insert;
                }

                PerkLevel nextPerkLevel = FindPerkLevel(perkLevels, pcPerk.PerkLevel + 1);

                if (nextPerkLevel == null)
                {
                    return;
                }
                pcPerk.PerkLevel++;
                DataService.SubmitDataChange(pcPerk, action);

                if (!freeUpgrade)
                {
                    player.UnallocatedSP -= nextPerkLevel.Price;
                    DataService.SubmitDataChange(player, DatabaseActionType.Update);
                }

                // Look for a perk feat to grant.
                var perkFeatToGrant = DataService.PerkFeat.GetByPerkIDAndLevelUnlockedOrDefault(perkID, pcPerk.PerkLevel);

                // Add the feat(s) to the player if it doesn't exist yet.
                if (perkFeatToGrant != null && _.GetHasFeat((Feat)perkFeatToGrant.FeatID, oPC.Object) == false)
                {
                    NWNXCreature.AddFeatByLevel(oPC, (Feat)perkFeatToGrant.FeatID, 1);

                    var qbs = NWNXPlayerQuickBarSlot.UseFeat((Feat)perkFeatToGrant.FeatID);

                    // Try to add the new feat to the player's hotbar.
                    if (NWNXPlayer.GetQuickBarSlot(oPC, 0).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 0, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 1).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 1, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 2).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 2, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 3).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 3, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 4).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 4, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 5).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 5, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 6).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 6, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 7).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 7, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 8).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 8, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 9).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 9, qbs);
                    }
                    else if (NWNXPlayer.GetQuickBarSlot(oPC, 10).ObjectType == QuickBarSlotType.Empty)
                    {
                        NWNXPlayer.SetQuickBarSlot(oPC, 10, qbs);
                    }
                }

                oPC.SendMessage(ColorTokenService.Green("Perk Purchased: " + perk.Name + " (Lvl. " + pcPerk.PerkLevel + ")"));

                MessageHub.Instance.Publish(new OnPerkUpgraded(oPC, perkID));

                var handler = GetPerkHandler(perkID);
                handler.OnPurchased(oPC, pcPerk.PerkLevel);
            }
            else
            {
                oPC.FloatingText(ColorTokenService.Red("You cannot purchase the perk at this time."));
            }
        }