/// <summary>
        /// Adds a set of exp to your character.
        /// </summary>
        /// <param name="character"></param>
        /// <param name="cexp"></param>
        /// <param name="jexp"></param>
        public static void Add(Character character, uint cexp, uint jexp, uint wexp)
        {
            byte clvl_up = 0;
            byte jlvl_up = 0;
            int result = 0;

            GiveWeaponExperience(character, wexp);

            try
            {
                #region Check for character-level

                if (character._level + 1 <= Singleton.experience.MaxCLVL)
                {
                    uint req_cexp = Singleton.experience.FindRequiredCexp((byte)(character._level + 1));
                    if (cexp > 0)
                    {
                        result |= 32;
                        result |= (character.Cexp + cexp >= req_cexp) ? 16 : 0;
                    }
                }
                else
                {
                    cexp = 0;
                }

                #endregion Check for character-level

                #region Check for job-level

                if (character.jlvl + 1 <= Singleton.experience.MaxJLVL)
                {
                    uint req_jexp = Singleton.experience.FindRequiredJexp((byte)(character.jlvl + 1));
                    if (jexp > 0)
                    {
                        result |= 4;
                        result |= (character.Jexp + jexp >= req_jexp) ? 1 : 0;
                    }
                }
                else
                {
                    jexp = 0;
                }

                #endregion Check for job-level

                #region Quick Lock For updates

                lock (character)
                {
                    //CALCULATE EXPERIENCE
                    uint newCexp = character.Cexp + cexp;
                    uint newJexp = character.Jexp + jexp;

                    //COMPUTE THE LEVEL DIFFERENCE (FORWARD ONLY)
                    if ((result & 1) == 1) jlvl_up = Singleton.experience.FindJlvlDifference(character.jlvl, newJexp);
                    if ((result & 16) == 16) clvl_up = Singleton.experience.FindClvlDifference(character._level, newCexp);

                    //RECALCULATE THE SP / HP ON CLVL UP
                    if (clvl_up > 0)
                    {
                        //CALCULATE BASE MAX HP/SP
                        ushort _HPMAX = Singleton.CharacterConfiguration.CalculateMaximumHP(character);
                        ushort _SPMAX = Singleton.CharacterConfiguration.CalculateMaximumSP(character);

                        //SUBSTRACT FROM CURRENT MAX HP/SP
                        character._status.MaxHP -= _HPMAX;
                        character._status.MaxSP -= _SPMAX;

                        //ADD CLVL'S
                        character._level += clvl_up;

                        //CALCULATE NEW BASE MAX HP/SP
                        ushort _HPMAX2 = Singleton.CharacterConfiguration.CalculateMaximumHP(character);
                        ushort _SPMAX2 = Singleton.CharacterConfiguration.CalculateMaximumSP(character);

                        //GENERATE STATS
                        character._status.MaxHP += _HPMAX2;
                        character._status.MaxSP += _SPMAX2;
                        character.stats.REMAINING += (ushort)(clvl_up * 2);

                        //FINALIZE SEND UPDATE
                        CommonFunctions.SendExtStats(character);
                        CommonFunctions.SendBattleStatus(character);

                        if (character.sessionParty != null)
                        {
                            SMSG_PARTYMEMBERCLVL spkt = new SMSG_PARTYMEMBERCLVL();
                            spkt.Index = 1;
                            spkt.ActorId = character.id;
                            spkt.Lp = character.jlvl;
                            foreach (Character target in character.sessionParty.GetCharacters())
                            {
                                spkt.SessionId = target.id;
                                target.client.Send((byte[])spkt);
                            }
                        }
                    }

                    if (jlvl_up > 0)
                    {
                        character.jlvl += jlvl_up;
                        if (character.sessionParty != null)
                        {
                            SMSG_PARTYMEMBERJLVL spkt = new SMSG_PARTYMEMBERJLVL();
                            spkt.Index = 1;
                            spkt.ActorId = character.id;
                            spkt.Jvl = character.jlvl;
                            foreach (Character target in character.sessionParty.GetCharacters())
                            {
                                spkt.SessionId = target.id;
                                target.client.Send((byte[])spkt);
                            }
                        }
                    }

                    //SET THE EXP
                    character.Cexp = newCexp;
                    character.Jexp = newJexp;

                    if (clvl_up > 0 || jlvl_up > 0)
                    {
                        //RESET HP AND SP
                        character.HP = character.HPMAX;
                        character.SP = character.SPMAX;
                    }

                    //FINALIZE SEND UPDATE
                    CommonFunctions.UpdateCharacterInfo(character, (byte)result);
                }

                #endregion Quick Lock For updates

                #region Structurize Buffer

                List<RelayPacket> buffer = new List<RelayPacket>();
                if (clvl_up > 0)
                {
                    //LEVEL UP CHARACTER-BASE LEVEL
                    SMSG_LEVELUP spkt = new SMSG_LEVELUP();
                    spkt.ActorID = character.id;
                    spkt.Levels = clvl_up;
                    spkt.LevelType = 1;
                    buffer.Add(spkt);
                }
                if (jlvl_up > 0)
                {
                    //LEVEL UP CHARACTER-JOB LEVEL
                    SMSG_LEVELUP spkt = new SMSG_LEVELUP();
                    spkt.ActorID = character.id;
                    spkt.Levels = jlvl_up;
                    spkt.LevelType = 2;
                    buffer.Add(spkt);
                }

                #endregion Structurize Buffer

                #region Flush all updates

                foreach (MapObject c in character.currentzone.GetObjectsInRegionalRange(character))
                    if (MapObject.IsPlayer(c))
                    {
                        Character current = c as Character;
                        foreach (RelayPacket buffered_packet in buffer)
                        {
                            buffered_packet.SessionId = current.id;
                            current.client.Send((byte[])buffered_packet);
                        }
                    }

                #endregion Flush all updates
            }
            catch (Exception e)
            {
                Trace.TraceError(e.ToString());
            }
        }
        /// <summary>
        /// Requests to change the job.
        /// </summary>
        /// <param name="cpkt"></param>
        private void CM_CHARACTER_JOBCHANGE(CMSG_CHANGEJOB cpkt)
        {
            JobChangeCollection collection = this.character.Tag as JobChangeCollection;
            if (collection == null || !collection.IsJobAvailable(cpkt.Job))
            {
                //Job change failed
                SMSG_JOBCHANGED spkt = new SMSG_JOBCHANGED();
                spkt.Job = this.character.job;
                spkt.Result = 1;
                spkt.SessionId = this.character.id;
                spkt.SourceActor = this.character.id;
                this.Send((byte[])spkt);
                return;
            }
            else if (collection.GetJobTrasferFee(cpkt.Job) > this.character.ZENY)
            {
                //Not enough money
                Common.Errors.GeneralErrorMessage(this.character, (uint)Generalerror.NotEnoughMoney);

                SMSG_JOBCHANGED spkt = new SMSG_JOBCHANGED();
                spkt.Job = this.character.job;
                spkt.Result = 1;
                spkt.SessionId = this.character.id;
                spkt.SourceActor = this.character.id;
                this.Send((byte[])spkt);
                return;
            }
            else
            {
                this.character.ZENY -= collection.GetJobTrasferFee(cpkt.Job);
                CommonFunctions.UpdateZeny(this.character);
            }

            //Helper variables
            List<int> EnabledEquipment = new List<int>();
            List<int> DisabledEquipment = new List<int>();
            bool ChangeWeapon = cpkt.ChangeWeapon == 1;
            bool IsActiveWeapon = this.character.weapons.IsActiveSlot(cpkt.WeaponSlot);
            Weapon selectedWeapon = this.character.weapons[cpkt.WeaponSlot];

            #region Update Character Information

            lock (this.character)
            {
                //Change the job and joblevel
                int hpbefore = Singleton.CharacterConfiguration.CalculateMaximumHP(this.character);
                int spbefore = Singleton.CharacterConfiguration.CalculateMaximumSP(this.character);
                this.character.CharacterJobLevel[this.character.job] = this.character.jlvl;
                this.character.jlvl = this.character.CharacterJobLevel[cpkt.Job];
                this.character.job = cpkt.Job;
                this.character.Jexp = Singleton.experience.FindRequiredJexp(this.character.jlvl);
                int hpafter = Singleton.CharacterConfiguration.CalculateMaximumHP(this.character);
                int spafter = Singleton.CharacterConfiguration.CalculateMaximumSP(this.character);
                this.character._status.CurrentHp += (ushort)(hpbefore - hpafter);
                this.character._status.CurrentSp += (ushort)(spbefore - spafter);
                this.character._status.Updates |= 1;
            }

            #endregion Update Character Information

            #region Refresh Weapon

            //Deapply current weapon info
            if (ChangeWeapon && IsActiveWeapon && selectedWeapon != null)
            {
                BattleStatus status = this.character._status;
                status.MaxWMAttack -= (ushort)selectedWeapon.Info.max_magic_attack;
                status.MinWMAttack -= (ushort)selectedWeapon.Info.min_magic_attack;
                status.MaxWPAttack -= (ushort)selectedWeapon.Info.max_short_attack;
                status.MinWPAttack -= (ushort)selectedWeapon.Info.min_short_attack;
                status.MaxWRAttack -= (ushort)selectedWeapon.Info.max_range_attack;
                status.MinWRAttack -= (ushort)selectedWeapon.Info.min_range_attack;
                status.Updates |= 2;

                //Reapplies alterstone additions
                for (int i = 0; i < 8; i++)
                {
                    uint addition = selectedWeapon.Slots[i];
                    if (addition > 0)
                    {
                        Singleton.Additions.DeapplyAddition(addition, character);
                    }
                }
            }

            #endregion Refresh Weapon

            #region Refresh Weapon

            if (ChangeWeapon && selectedWeapon != null)
            {
                Singleton.Weapons.ChangeWeapon(cpkt.Job, cpkt.PostFix, selectedWeapon);
                SMSG_WEAPONCHANGE spkt = new SMSG_WEAPONCHANGE();
                spkt.Auge = selectedWeapon._augeskill;
                spkt.SessionId = this.character.id;
                spkt.Suffix = cpkt.PostFix;
                spkt.Index = cpkt.WeaponSlot;
                spkt.WeaponType = (byte)selectedWeapon._type;
                this.Send((byte[])spkt);
            }

            #endregion Refresh Weapon

            #region Refresh Skills

            {
                //Remove all skills
                foreach (Skill skill in this.character.learnedskills)
                {
                    //Remove the skill
                    SMSG_SKILLREMOVE spkt = new SMSG_SKILLREMOVE();
                    spkt.Unknown = skill.info.skilltype;
                    spkt.SessionId = this.character.id;
                    spkt.SkillId = skill.info.skillid;
                    this.Send((byte[])spkt);

                    //Save only experience if it's maxed-out.
                    Singleton.Database.UpdateSkill(this.character, skill.Id,
                        (skill.Experience == skill.info.maximumexperience) ? skill.info.maximumexperience : 0);

                    //Deapply passive skills
                    bool canUse = Singleton.SpellManager.CanUse(this.character, skill.info);
                    if (skill.info.skilltype == 2 && canUse)
                        Singleton.Additions.DeapplyAddition(skill.info.addition, character);
                }

                //Remove all learned skills in an instant
                this.character.learnedskills.Clear();
                Singleton.Database.LoadSkills(this.character);

                //Retrieve job speciafic skills
                foreach (Skill skill in this.character.learnedskills)
                {
                    //Add the skill
                    SMSG_SKILLADD spkt = new SMSG_SKILLADD();
                    spkt.SessionId = this.character.id;
                    spkt.SkillId = skill.info.skillid;
                    spkt.Slot = 0;
                    this.Send((byte[])spkt);

                    //Deapply passive skills
                    bool canUse = Singleton.SpellManager.CanUse(this.character, skill.info);
                    if (skill.info.skilltype == 2 && canUse)
                        Singleton.Additions.ApplyAddition(skill.info.addition, character);
                }
            }

            #endregion Refresh Skills

            #region Refresh Weapon

            //Apply current weapon info
            if (ChangeWeapon && IsActiveWeapon && selectedWeapon != null)
            {
                //Apply status
                BattleStatus status = this.character._status;
                status.MaxWMAttack += (ushort)selectedWeapon.Info.max_magic_attack;
                status.MinWMAttack += (ushort)selectedWeapon.Info.min_magic_attack;
                status.MaxWPAttack += (ushort)selectedWeapon.Info.max_short_attack;
                status.MinWPAttack += (ushort)selectedWeapon.Info.min_short_attack;
                status.MaxWRAttack += (ushort)selectedWeapon.Info.max_range_attack;
                status.MinWRAttack += (ushort)selectedWeapon.Info.min_range_attack;
                status.Updates |= 2;

                //Reapplies alterstone additions
                for (int i = 0; i < 8; i++)
                {
                    uint addition = selectedWeapon.Slots[i];
                    if (addition > 0)
                    {
                        Singleton.Additions.ApplyAddition(addition, character);
                    }
                }
            }

            #endregion Refresh Weapon

            #region Refresh Equipment

            {
                //Disable equipment
                for (int i = 0; i < 16; i++)
                {
                    //Verify if item exists
                    Rag2Item item = this.character.Equipment[i];
                    if (item == null || item.info == null) continue;

                    //Verify if the item changed states
                    bool Active = item.active == 1;
                    bool NewActive = this.character.jlvl >= item.info.JobRequirement[cpkt.Job - 1];
                    if (Active == NewActive) continue;
                    item.active = (byte)((NewActive == true) ? 1 : 0);

                    //Adjust the item
                    SMSG_ITEMADJUST spkt = new SMSG_ITEMADJUST();
                    spkt.Container = 1;
                    spkt.Function = 5;
                    spkt.Slot = (byte)i;
                    spkt.SessionId = this.character.id;
                    spkt.Value = item.active;
                    this.Send((byte[])spkt);

                    //Deapply additions
                    if (NewActive)
                    {
                        EnabledEquipment.Add(i);
                        Singleton.Additions.ApplyAddition(item.info.option_id, character);
                    }
                    else
                    {
                        DisabledEquipment.Add(i);
                        Singleton.Additions.DeapplyAddition(item.info.option_id, character);
                    }
                }

                //Update other stats
                //Common.Internal.CheckWeaponary(this.character);
                //CommonFunctions.UpdateCharacterInfo(this.character, 0);
                //CommonFunctions.SendBattleStatus(this.character);
            }

            #endregion Refresh Equipment

            #region Refresh Appereance

            {
                Regiontree tree = this.character.currentzone.Regiontree;
                foreach (Character regionObject in tree.SearchActors(SearchFlags.Characters))
                {
                    SMSG_JOBCHANGED spkt = new SMSG_JOBCHANGED();
                    spkt.SessionId = regionObject.id;
                    spkt.SourceActor = this.character.id;
                    spkt.Job = cpkt.Job;
                    regionObject.client.Send((byte[])spkt);

                    if (regionObject.id != this.character.id)
                    {
                        if (IsActiveWeapon)
                        {
                            uint auge = (character.FindRequiredRootSkill(selectedWeapon.Info.weapon_skill)) ? selectedWeapon._augeskill : 0;
                            SMSG_SHOWWEAPON spkt2 = new SMSG_SHOWWEAPON();
                            spkt2.ActorID = this.character.id;
                            spkt2.AugeID = auge;
                            spkt2.SessionId = regionObject.id;
                            regionObject.client.Send((byte[])spkt2);
                        }

                        foreach (byte i in EnabledEquipment)
                        {
                            Rag2Item item = this.character.Equipment[i];
                            SMSG_CHANGEEQUIPMENT spkt2 = new SMSG_CHANGEEQUIPMENT();
                            spkt2.SessionId = regionObject.id;
                            spkt2.ActorID = this.character.id;
                            spkt2.Slot = i;
                            spkt2.ItemID = item.info.item;
                            spkt2.Dye = item.dyecolor;
                            regionObject.client.Send((byte[])spkt2);
                        }

                        foreach (byte i in DisabledEquipment)
                        {
                            SMSG_CHANGEEQUIPMENT spkt2 = new SMSG_CHANGEEQUIPMENT();
                            spkt2.SessionId = regionObject.id;
                            spkt2.ActorID = this.character.id;
                            spkt2.Slot = i;
                            spkt2.ItemID = 0;
                            spkt2.Dye = 0;
                            regionObject.client.Send((byte[])spkt2);
                        }
                    }
                }
            }

            #endregion Refresh Appereance

            #region Refresh Party

            {
                //Process party members
                SMSG_PARTYMEMBERJOB spkt = new SMSG_PARTYMEMBERJOB();
                spkt.Job = cpkt.Job;
                spkt.SessionId = this.character.id;
                spkt.ActorId = this.character.id;
                spkt.Index = 1;

                SMSG_PARTYMEMBERJLVL spkt2 = new SMSG_PARTYMEMBERJLVL();
                spkt2.Jvl = this.character.jlvl;
                spkt2.ActorId = this.character.id;
                spkt2.Index = 1;

                if (this.character.sessionParty != null)
                {
                    foreach (Character target in this.character.sessionParty.GetCharacters())
                    {
                        //Send over Job change
                        spkt.SessionId = target.id;
                        target.client.Send((byte[])spkt);

                        //Send over jlvl change
                        spkt2.SessionId = target.id;
                        target.client.Send((byte[])spkt2);
                    }
                }
            }

            #endregion Refresh Party

            #region Refresh LifeCycle

            Tasks.LifeCycle.Update(this.character);

            #endregion Refresh LifeCycle
        }