예제 #1
0
        public void SpendAllAvailableSkillXp(CreatureSkill skill, bool sendNetworkPropertyUpdate = true)
        {
            var xpList = GetXPTable(skill.AdvancementClass);

            if (xpList == null)
            {
                return;
            }

            while (true)
            {
                uint currentRankXp = xpList[Convert.ToInt32(skill.Ranks)];
                uint rank10;

                if (skill.Ranks + 10 >= (xpList.Count))
                {
                    var rank10Offset = 10 - (Convert.ToInt32(skill.Ranks + 10) - (xpList.Count - 1));
                    rank10 = xpList[Convert.ToInt32(skill.Ranks) + rank10Offset] - currentRankXp;
                }
                else
                {
                    rank10 = xpList[Convert.ToInt32(skill.Ranks) + 10] - currentRankXp;
                }

                if (SpendSkillXp(skill, rank10, false, sendNetworkPropertyUpdate) == 0)
                {
                    break;
                }
            }
        }
예제 #2
0
        /// <summary>
        /// Adds experience points to a skill
        /// </summary>
        /// <returns>0 if it failed, total skill experience if successful</returns>
        private uint SpendSkillXp(CreatureSkill skill, uint amount, bool sendNetworkPropertyUpdate = true)
        {
            uint result = 0u;

            var xpList = GetXPTable(skill.AdvancementClass);

            if (xpList == null)
            {
                return(result);
            }

            // do not advance if we cannot spend xp to rank up our skill by 1 point
            if (skill.Ranks >= (xpList.Count - 1))
            {
                return(result);
            }

            ushort rankUps = (ushort)(Player.CalcSkillRank(skill.AdvancementClass, skill.ExperienceSpent + amount) - skill.Ranks);

            if (SpendXP(amount, sendNetworkPropertyUpdate))
            {
                if (rankUps > 0)
                {
                    skill.Ranks += rankUps;
                }

                skill.ExperienceSpent += amount;
                result = skill.ExperienceSpent;
            }

            return(result);
        }
예제 #3
0
    public void SetSkill(CreatureSkill cSkill)
    {
        edittingSkill       = cSkill;
        edittingProgramInfo = new CreatureSkill.ProgramInfo(cSkill.programInfo);

        string leftPartTextText = string.Format(
            leftPartTextTemplate,
            cSkill.skill.name,
            cSkill.skill.description,
            cSkill.skill.damageType.ToString(),
            cSkill.buff != null ? cSkill.buff.name : "/",
            cSkill.level,
            cSkill.exp.ToString("f1"),
            cSkill.expToNextLevel.ToString("f1")
            );

        string rightPartTextText = string.Format(
            rightPartTextTemplate,
            cSkill.originDamage.ToString("f1"),
            (cSkill.programInfo.damageDelta * CreatureSkill.ProgramInfo.damageDeltaPercent * 100f).ToString("+#;-#;0"),
            cSkill.originCDTime.ToString("f1"),
            (cSkill.programInfo.cdTimeDelta * CreatureSkill.ProgramInfo.cdTimeDeltaPercent * 100f).ToString("+#;-#;0"),
            cSkill.originBulletForceNorm.ToString("f1"),
            (cSkill.programInfo.bulletForceNormDelta * CreatureSkill.ProgramInfo.bulletForceNormDeltaPercent * 100f).ToString("+#;-#;0"),
            (cSkill.originBulletAngle + cSkill.programInfo.bulletAngle).ToString("f1"),
            cSkill.originActionSpeedMultiplier.ToString("f1"),
            (cSkill.programInfo.actionSpeedDelta * CreatureSkill.ProgramInfo.actionSpeedDeltaPercent * 100f).ToString("+#;-#;0"),
            cSkill.programInfo.balanceValue
            );

        leftPartText.text  = leftPartTextText;
        rightPartText.text = rightPartTextText;
    }
예제 #4
0
    public override void UseSkill(CreatureSkill skill, Creature attackSource)
    {
        base.UseSkill(skill, attackSource);
        Animator animator = GetComponent <Animator>();

        animator.speed = skill.calActionSpeedMultiplier * animator.speed;
        animator.Play(skill.skill.idName);
    }
예제 #5
0
 public static void OnSuccessUse(Player player, CreatureSkill skill, int difficulty)
 {
     if (difficulty < 0)
     {
         log.Error($"Proficiency.OnSuccessUse({player.Name}, {skill.Skill}, {difficulty}) - difficulty cannot be negative");
         return;
     }
     OnSuccessUse(player, skill, (uint)difficulty);
 }
예제 #6
0
        public static void OnSuccessUse(Player player, CreatureSkill skill, uint difficulty)
        {
            //Console.WriteLine($"Proficiency.OnSuccessUse({player.Name}, {skill.Skill}, targetDiff: {difficulty})");

            // TODO: this formula still probably needs some work to match up with retail truly...

            // possible todo: does this only apply to players?
            // ie., can monsters still level up from skill usage, or killing players?
            // it was possible on release, but i think they might have removed that feature?

            // ensure skill is at least trained
            if (skill.AdvancementClass < SkillAdvancementClass.Trained)
            {
                return;
            }

            var last_difficulty = skill.BiotaPropertiesSkill.ResistanceAtLastCheck;
            var last_used_time  = skill.BiotaPropertiesSkill.LastUsedTime;

            var currentTime = Time.GetUnixTime();

            var timeDiff = currentTime - last_used_time;

            var difficulty_check = difficulty > last_difficulty;
            var time_check       = timeDiff >= FullTime.TotalSeconds;

            if (difficulty_check || time_check)
            {
                // todo: not independent variables?
                // always scale if timeDiff < FullTime?
                var timeScale = 1.0f;
                if (!time_check)
                {
                    // 10 mins elapsed from 15 min FullTime:
                    // 0.66f timeScale
                    timeScale = (float)(timeDiff / FullTime.TotalSeconds);

                    // any rng involved?
                }

                skill.BiotaPropertiesSkill.ResistanceAtLastCheck = difficulty;
                skill.BiotaPropertiesSkill.LastUsedTime          = currentTime;

                player.ChangesDetected = true;

                var pp = (uint)Math.Round(difficulty * timeScale);
                var cp = (uint)Math.Round(pp * 0.1f);   // cp = 10% PP

                //Console.WriteLine($"Earned {pp} PP ({skill.Skill})");

                // send PP to player as skill XP
                player.RaiseSkillGameAction(skill.Skill, pp, true);

                // send CP to player as unassigned XP
                player.GrantXP(cp, XpType.Proficiency, false);
            }
        }
예제 #7
0
    public void SetSkillList(string[] skills, int[] skillExps)
    {
        int len = Mathf.Min(skills.Length, skillExps.Length);

        cSkillList = new CreatureSkill[len];
        for (int i = 0; i < len; ++i)
        {
            cSkillList[i] = new CreatureSkill(JsonManager.instance.skillDict[skills[i]], skillExps[i]);
        }
    }
예제 #8
0
    public override void UseSkill(CreatureSkill skill, Creature attackSource)
    {
        base.UseSkill(skill, attackSource);
        Animator animator = GetComponent <Animator>();

        animator.runtimeAnimatorController = BulletDict.instance.animatorControllerDict[skill.skill.idName];
        if (animator.runtimeAnimatorController != null)
        {
            animator.Play("Flying");
        }
    }
예제 #9
0
        public void SpendAllAvailableSkillXp(CreatureSkill creatureSkill, bool sendNetworkUpdate = true)
        {
            var amountRemaining = creatureSkill.ExperienceLeft;

            if (amountRemaining > AvailableExperience)
            {
                amountRemaining = (uint)AvailableExperience;
            }

            SpendSkillXp(creatureSkill, amountRemaining, sendNetworkUpdate);
        }
예제 #10
0
        /// <summary>
        /// Returns the remaining XP required to the next skill level
        /// </summary>
        public uint?GetXpToNextRank(CreatureSkill skill)
        {
            if (skill.AdvancementClass < SkillAdvancementClass.Trained || skill.IsMaxRank)
            {
                return(null);
            }

            var skillXPTable = GetSkillXPTable(skill.AdvancementClass);

            return(skillXPTable[skill.Ranks + 1] - skill.ExperienceSpent);
        }
예제 #11
0
        /// <summary>
        /// Returns the remaining XP required to the next skill level
        /// </summary>
        public uint GetXpToNextRank(CreatureSkill skill)
        {
            var xpList = GetXPTable(skill.AdvancementClass);

            if (xpList != null)
            {
                return(xpList[Convert.ToInt32(skill.Ranks) + 1] - skill.ExperienceSpent);
            }
            else
            {
                return(uint.MaxValue);
            }
        }
예제 #12
0
        public uint CalculateManaUsage(Creature caster, SpellBase spell, WorldObject target = null)
        {
            var items = new List <WorldObject>();

            if ((target as Player) != null)
            {
                items = (target as Player).GetAllWieldedItems();
            }

            CreatureSkill mc = caster.GetCreatureSkill(Skill.ManaConversion);
            double        z  = mc.Current;
            double        baseManaPercent = 1;

            if (z > spell.Power)
            {
                baseManaPercent = spell.Power / z;
            }
            double preCost  = 0;
            uint   manaUsed = 0;

            if ((int)Math.Floor(baseManaPercent) == 1)
            {
                preCost  = spell.BaseMana;
                manaUsed = (uint)preCost;
            }
            else
            {
                if ((spell.School == MagicSchool.ItemEnchantment) && (spell.MetaSpellType == SpellType.Enchantment))
                {
                    int count = 1;
                    if ((target as Player) != null)
                    {
                        count = items.Count;
                    }

                    preCost = (spell.BaseMana + (spell.ManaMod * items.Count)) * baseManaPercent;
                }
                else
                {
                    preCost = spell.BaseMana * baseManaPercent;
                }
                if (preCost < 1)
                {
                    preCost = 1;
                }
                manaUsed = (uint)Physics.Common.Random.RollDice(1, (int)preCost);
            }

            return(manaUsed);
        }
예제 #13
0
        /// <summary>
        /// Returns the skill with the largest current value (buffed)
        /// </summary>
        public CreatureSkill GetMaxSkill(List <Skill> skills)
        {
            CreatureSkill maxSkill = null;

            foreach (var skill in skills)
            {
                var creatureSkill = GetCreatureSkill(skill);
                if (maxSkill == null || creatureSkill.Current > maxSkill.Current)
                {
                    maxSkill = creatureSkill;
                }
            }
            return(maxSkill);
        }
예제 #14
0
        public void SpendAllAvailableSkillXp(CreatureSkill creatureSkill, bool sendNetworkUpdate = true)
        {
            var amountRemaining = creatureSkill.ExperienceLeft;

            if (amountRemaining > AvailableExperience)
            {
                amountRemaining = (uint)AvailableExperience;
            }

            SpendSkillXp(creatureSkill, amountRemaining, sendNetworkUpdate);
            if (sendNetworkUpdate)
            {
                Session.Network.EnqueueSend(new GameMessagePrivateUpdateSkill(this, creatureSkill));
            }
        }
예제 #15
0
        public static void ShowInfo(Creature observed, Spell spell, CreatureSkill skill, float criticalChance, bool criticalHit, bool critDefended, bool overpower, float weaponCritDamageMod,
                                    float magicSkillBonus, int baseDamage, float damageBonus, float elementalDamageMod, float slayerMod,
                                    float weaponResistanceMod, float resistanceMod, float absorbMod,
                                    float lifeProjectileDamage, float lifeMagicDamage, float finalDamage)
        {
            var observer = PlayerManager.GetOnlinePlayer(observed.DebugDamageTarget);

            if (observer == null)
            {
                observed.DebugDamage = Creature.DebugDamageType.None;
                return;
            }

            var info = $"Skill: {skill.Skill.ToSentence()}\n";

            info += $"CriticalChance: {criticalChance}\n";
            info += $"CriticalHit: {criticalHit}\n";
            info += $"CriticalDefended: {critDefended}\n";
            info += $"Overpower: {overpower}\n";

            if (spell.MetaSpellType == ACE.Entity.Enum.SpellType.LifeProjectile)
            {
                // life magic projectile
                info += $"LifeProjectileDamage: {lifeProjectileDamage}\n";
                info += $"DamageRatio: {spell.DamageRatio}\n";
                info += $"LifeMagicDamage: {lifeMagicDamage}\n";
            }
            else
            {
                // war/void projectile
                var difficulty = Math.Min(spell.Power, 350);
                info += $"Difficulty: {difficulty}\n";
                info += $"SkillBonus: {magicSkillBonus}\n";
                info += $"BaseDamageRange: {spell.MinDamage} - {spell.MaxDamage}\n";
                info += $"BaseDamage: {baseDamage}\n";
                info += $"DamageType: {spell.DamageType}\n";
            }

            info += $"WeaponCritDamageMod: {weaponCritDamageMod}\n";
            info += $"DamageBonus: {damageBonus}\n";
            info += $"ElementalDamageMod: {elementalDamageMod}\n";
            info += $"SlayerMod: {slayerMod}\n";
            info += $"WeaponResistanceMod: {weaponResistanceMod}\n";
            info += $"ResistanceMod: {resistanceMod}\n";
            info += $"AbsorbMod: {absorbMod}";

            observer.Session.Network.EnqueueSend(new GameMessageSystemChat(info, ChatMessageType.Broadcast));
        }
예제 #16
0
    /*
     * 该方法逻辑较为复杂
     * 1. 如果该技能使用武器
     *  1. 根据ProgramInfo更改武器、人物播放动画的速度
     *  2. 调用attack.UseSkill
     * 2. 如果该技能使用子弹
     *  1. 根据子弹信息生成子弹
     *  2. 将子弹位置放到自身附近
     *  3. 调用attack.UseSkill
     *  4. 根据ProgramInfo决定给予子弹的力(包括力的方向) (发射子弹)
     */
    public void UseSkill(int index)
    {
        if (cSkillList[index].isCooling)
        {
            return;
        }

        // 播放自身动画
        animator.Play("Attack");
        CreatureSkill skill = cSkillList[index];

        skill.SetCooling();

        if ((skill.skill.attackType & Skill.AttackType.Weapon) != 0)
        {
            attack.UseSkill(skill, this);
        }
        if ((skill.skill.attackType & Skill.AttackType.Bullet) != 0)
        {
            Debug.Log("Spawning a bullet");
            // Spawn a bullet
            BulletInfo bulletInfo = BulletDict.instance.itemDict[skill.skill.idName];
            GameObject bullet     = Instantiate(GameManager.instance.bulletPrefab);
            Vector3    vec        = transform.position;
            // vec.y += 2.0f;
            bullet.transform.position = vec;
            bullet.name = bulletInfo.idName;

            bulletInfo.rigidbodyPara.ApplyToGameObject(bullet);

            // Add force
            Attack attack = bullet.GetComponent <Attack>();
            // attack.destroyGObjOnTriggerEnter = true;
            attack.UseSkill(skill, this);
            Rigidbody2D rb = bullet.GetComponent <Rigidbody2D>();
            Debug.Log("Force: " + skill.calBulletForce);
            if (isFacingRight)
            {
                rb.AddForce(skill.calBulletForce);
            }
            else
            {
                rb.AddForce(-skill.calBulletForce);
                bullet.transform.localRotation = Quaternion.Euler(0, 180, 0);
            }
        }
    }
예제 #17
0
        public GameMessagePrivateUpdateSkill(WorldObject worldObject, CreatureSkill creatureSkill)
            : base(GameMessageOpcode.PrivateUpdateSkill, GameMessageGroup.UIQueue)
        {
            Writer.Write(worldObject.Sequences.GetNextSequence(Sequence.SequenceType.UpdateSkill, creatureSkill.Skill));

            ushort adjustPP = 1;            // If this is not 0, it appears to trigger the initLevel to be treated as extra XP applied to the skill

            Writer.Write((uint)creatureSkill.Skill);
            Writer.Write(creatureSkill.Ranks);
            Writer.Write(adjustPP);
            Writer.Write((uint)creatureSkill.AdvancementClass);
            Writer.Write(creatureSkill.ExperienceSpent);

            Writer.Write(creatureSkill.InitLevel);            // starting point for advancement of the skill (eg. bonus points)
            Writer.Write(creatureSkill.PropertiesSkill.ResistanceAtLastCheck);
            Writer.Write(creatureSkill.PropertiesSkill.LastUsedTime);
        }
예제 #18
0
        /// <summary>
        /// Will return true if the skill was added, or false if the skill already exists.
        /// </summary>
        public bool AddSkill(Skill skill, SkillStatus skillStatus)
        {
            var result = Biota.BiotaPropertiesSkill.FirstOrDefault(x => x.Type == (uint)skill);

            if (result == null)
            {
                result = new BiotaPropertiesSkill {
                    ObjectId = Biota.Id, Type = (ushort)skill, SAC = (uint)skillStatus
                };

                Biota.BiotaPropertiesSkill.Add(result);

                Skills[skill] = new CreatureSkill(this, skill);

                return(true);
            }

            return(false);
        }
예제 #19
0
        /// <summary>
        /// This will get a CreatureSkill wrapper around the BiotaPropertiesSkill record for this player.
        /// If the skill doesn't exist for this Biota, one will be created with a status of Untrained.
        /// </summary>
        public CreatureSkill GetCreatureSkill(Skill skill)
        {
            if (Skills.TryGetValue(skill, out var value))
            {
                return(value);
            }

            var biotaPropertiesSkill = Biota.GetOrAddSkill((ushort)skill, BiotaDatabaseLock, out var skillAdded);

            if (skillAdded)
            {
                biotaPropertiesSkill.SAC = (uint)SkillAdvancementClass.Untrained;
                ChangesDetected          = true;
            }

            Skills[skill] = new CreatureSkill(this, biotaPropertiesSkill);

            return(Skills[skill]);
        }
예제 #20
0
        private bool SpendSkillXp(CreatureSkill creatureSkill, uint amount, bool sendNetworkUpdate = true)
        {
            var skillXPTable = GetSkillXPTable(creatureSkill.AdvancementClass);

            if (skillXPTable == null)
            {
                log.Error($"{Name}.SpendSkillXp({creatureSkill.Skill}, {amount}) - player tried to raise {creatureSkill.AdvancementClass} skill");
                return(false);
            }

            // ensure skill is not already max rank
            if (creatureSkill.IsMaxRank)
            {
                log.Error($"{Name}.SpendSkillXp({creatureSkill.Skill}, {amount}) - player tried to raise skill beyond max rank");
                return(false);
            }

            // the client should already handle this naturally,
            // but ensure player can't spend xp beyond the max rank
            var amountToEnd = creatureSkill.ExperienceLeft;

            if (amount > amountToEnd)
            {
                log.Error($"{Name}.SpendSkillXp({creatureSkill.Skill}, {amount}) - player tried to raise skill beyond {amountToEnd} experience");
                return(false);   // returning error here, instead of setting amount to amountToEnd
            }

            // everything looks good at this point,
            // spend xp on skill
            if (!SpendXP(amount, sendNetworkUpdate))
            {
                log.Error($"{Name}.SpendSkillXp({creatureSkill.Skill}, {amount}) - SpendXP failed");
                return(false);
            }

            creatureSkill.ExperienceSpent += amount;

            // calculate new rank
            creatureSkill.Ranks = (ushort)CalcSkillRank(creatureSkill.AdvancementClass, creatureSkill.ExperienceSpent);

            return(true);
        }
예제 #21
0
    public virtual void UseSkill(CreatureSkill skill, Creature attackSource)
    {
        skill.exp += 10f;
        // skillIndex = index;
        this.attackSource = attackSource;
        // this.skill = GameManager.instance.skillDict.itemDict[creature.skillList[skillIndex]];
        this.cSkill = skill;

        // 计算伤害
        float basicDamage = 0f;

        if (skill.skill.damageType == Skill.DamageType.Sword)
        {
            basicDamage = attackSource.currentInfo.sword;
        }
        else if (skill.skill.damageType == Skill.DamageType.Magic)
        {
            basicDamage = attackSource.currentInfo.magic;
        }
        damage = skill.calDamage * basicDamage;
    }
예제 #22
0
        /// <summary>
        /// This will get a CreatureSkill wrapper around the BiotaPropertiesSkill record for this player.
        /// If the skill doesn't exist for this Biota, one will be created with a status of Untrained.
        /// </summary>
        /// <param name="add">If the skill doesn't exist for this biota, adds it</param>
        public CreatureSkill GetCreatureSkill(Skill skill, bool add = true)
        {
            if (Skills.TryGetValue(skill, out var value))
            {
                return(value);
            }

            PropertiesSkill propertiesSkill;

            if (add)
            {
                propertiesSkill = Biota.GetOrAddSkill(skill, BiotaDatabaseLock, out var skillAdded);

                if (skillAdded)
                {
                    propertiesSkill.SAC = SkillAdvancementClass.Untrained;
                    ChangesDetected     = true;
                }

                Skills[skill] = new CreatureSkill(this, skill, propertiesSkill);
            }
            else
            {
                propertiesSkill = Biota.GetSkill(skill, BiotaDatabaseLock);

                if (propertiesSkill != null)
                {
                    Skills[skill] = new CreatureSkill(this, skill, propertiesSkill);
                }
                else
                {
                    return(null);
                }
            }

            return(Skills[skill]);
        }
예제 #23
0
        /// <summary>
        /// Adds experience points to a skill
        /// </summary>
        /// <remarks>
        ///     Known Issues:
        ///         1. Earned XP usage in ranks besides 1 or 10 need to be accounted for.
        /// </remarks>
        /// <returns>0 if it failed, total skill experience if successful</returns>
        private uint SpendSkillXp(CreatureSkill skill, uint amount, bool usage = false, bool sendNetworkPropertyUpdate = true)
        {
            uint result = 0u;

            var xpList = GetXPTable(skill.AdvancementClass);

            if (xpList == null)
            {
                return(result);
            }

            // do not advance if we cannot spend xp to rank up our skill by 1 point
            if (skill.Ranks >= (xpList.Count - 1))
            {
                return(result);
            }

            ushort rankUps       = 0;
            uint   currentRankXp = skill.ExperienceSpent;
            uint   rank1         = xpList[Convert.ToInt32(skill.Ranks) + 1] - currentRankXp;
            uint   rank10;
            ushort rank10Offset = 0;

            if (skill.Ranks + 10 >= (xpList.Count))
            {
                rank10Offset = (ushort)(10 - ((skill.Ranks + 10) - (xpList.Count - 1)));
                rank10       = xpList[skill.Ranks + rank10Offset] - currentRankXp;
            }
            else
            {
                rank10 = xpList[skill.Ranks + 10] - currentRankXp;
            }

            if (amount >= rank10)
            {
                if (rank10Offset > 0)
                {
                    rankUps = rank10Offset;
                }
                else
                {
                    rankUps = 10;
                }
            }
            else if (amount >= rank1)
            {
                rankUps = 1;
            }

            if (!usage)
            {
                if (SpendXP(amount, sendNetworkPropertyUpdate))
                {
                    if (rankUps > 0)
                    {
                        skill.Ranks += rankUps;
                    }

                    skill.ExperienceSpent += amount;
                    result = skill.ExperienceSpent;
                }
            }
            else
            {
                if (rankUps > 0)
                {
                    skill.Ranks += rankUps;
                }

                skill.ExperienceSpent += amount;
                result = skill.ExperienceSpent;
            }

            return(result);
        }
예제 #24
0
        /// <summary>
        /// Calculates the damage for a spell projectile
        /// Used by war magic, void magic, and life magic projectiles
        /// </summary>
        public double?CalculateDamage(WorldObject _source, Creature target, ref bool criticalHit)
        {
            var source       = _source as Creature;
            var sourcePlayer = source as Player;
            var targetPlayer = target as Player;

            if (source == null || targetPlayer != null && targetPlayer.Invincible == true)
            {
                return(null);
            }

            // target already dead?
            if (target.Health.Current <= 0)
            {
                return(-1);
            }

            // check lifestone protection
            if (targetPlayer != null && targetPlayer.UnderLifestoneProtection)
            {
                targetPlayer.HandleLifestoneProtection();
                return(null);
            }

            double damageBonus = 0.0f, warSkillBonus = 0.0f, finalDamage = 0.0f;

            var resistanceType = GetResistanceType(Spell.DamageType);

            var resisted = source.ResistSpell(target, Spell);

            if (resisted != null && resisted == true)
            {
                return(null);
            }

            CreatureSkill attackSkill    = null;
            var           sourceCreature = source as Creature;

            if (sourceCreature != null)
            {
                attackSkill = sourceCreature.GetCreatureSkill(Spell.School);
            }

            // critical hit
            var critical = GetWeaponMagicCritFrequencyModifier(source, attackSkill);

            if (ThreadSafeRandom.Next(0.0f, 1.0f) < critical)
            {
                criticalHit = true;
            }

            bool isPVP = sourcePlayer != null && targetPlayer != null;

            var elementalDmgBonus = GetCasterElementalDamageModifier(source, target, Spell.DamageType);

            // Possible 2x + damage bonus for the slayer property
            var slayerBonus = GetWeaponCreatureSlayerModifier(source, target);

            // life magic projectiles: ie., martyr's hecatomb
            if (Spell.School == MagicSchool.LifeMagic)
            {
                var lifeMagicDamage = LifeProjectileDamage * Spell.DamageRatio;

                // could life magic projectiles crit?
                // if so, did they use the same 1.5x formula as war magic, instead of 2.0x?
                if (criticalHit)
                {
                    damageBonus = lifeMagicDamage * 0.5f * GetWeaponCritMultiplierModifier(source, attackSkill);
                }

                finalDamage = (lifeMagicDamage + damageBonus) * elementalDmgBonus * slayerBonus;
                return(finalDamage);
            }
            // war magic projectiles (and void currently)
            else
            {
                if (criticalHit)
                {
                    if (isPVP) // PvP: 50% of the MIN damage added to normal damage roll
                    {
                        damageBonus = Spell.MinDamage * 0.5f;
                    }
                    else   // PvE: 50% of the MAX damage added to normal damage roll
                    {
                        damageBonus = Spell.MaxDamage * 0.5f;
                    }

                    var critDamageMod = GetWeaponCritMultiplierModifier(source, attackSkill);

                    damageBonus *= critDamageMod;
                }

                /* War Magic skill-based damage bonus
                 * http://acpedia.org/wiki/Announcements_-_2002/08_-_Atonement#Letter_to_the_Players
                 */
                if (sourcePlayer != null && Spell.School == MagicSchool.WarMagic)
                {
                    var warSkill = source.GetCreatureSkill(Spell.School).Current;
                    if (warSkill > Spell.Power)
                    {
                        // Bonus clamped to a maximum of 50%
                        var percentageBonus = Math.Clamp((warSkill - Spell.Power) / 100.0f, 0.0f, 0.5f);
                        warSkillBonus = Spell.MinDamage * percentageBonus;
                    }
                }
                var baseDamage = ThreadSafeRandom.Next(Spell.MinDamage, Spell.MaxDamage);

                finalDamage  = baseDamage + damageBonus + warSkillBonus;
                finalDamage *= target.GetNaturalResistance(resistanceType, GetWeaponResistanceModifier(source, attackSkill, Spell.DamageType))
                               * elementalDmgBonus * slayerBonus;

                return(finalDamage);
            }
        }
예제 #25
0
        /// <summary>
        /// Calculates the damage for a spell projectile
        /// Used by war magic, void magic, and life magic projectiles
        /// </summary>
        public double?CalculateDamage(WorldObject source, WorldObject caster, Creature target, ref bool criticalHit, ref bool critDefended, ref bool overpower)
        {
            var sourcePlayer = source as Player;
            var targetPlayer = target as Player;

            if (source == null || targetPlayer != null && targetPlayer.Invincible == true)
            {
                return(null);
            }

            // target already dead?
            if (target.Health.Current <= 0)
            {
                return(-1);
            }

            // check lifestone protection
            if (targetPlayer != null && targetPlayer.UnderLifestoneProtection)
            {
                if (sourcePlayer != null)
                {
                    sourcePlayer.Session.Network.EnqueueSend(new GameMessageSystemChat($"The Lifestone's magic protects {targetPlayer.Name} from the attack!", ChatMessageType.Magic));
                }

                targetPlayer.HandleLifestoneProtection();
                return(null);
            }

            double damageBonus = 0.0f, warSkillBonus = 0.0f, finalDamage = 0.0f;

            var resistanceType = Creature.GetResistanceType(Spell.DamageType);

            var sourceCreature = source as Creature;

            if (sourceCreature?.Overpower != null)
            {
                overpower = Creature.GetOverpower(sourceCreature, target);
            }

            var resisted = source.ResistSpell(target, Spell, caster);

            if (resisted == true && !overpower)
            {
                return(null);
            }

            CreatureSkill attackSkill = null;

            if (sourceCreature != null)
            {
                attackSkill = sourceCreature.GetCreatureSkill(Spell.School);
            }

            // critical hit
            var critical = GetWeaponMagicCritFrequency(sourceCreature, attackSkill, target);

            if (ThreadSafeRandom.Next(0.0f, 1.0f) < critical)
            {
                if (targetPlayer != null && targetPlayer.AugmentationCriticalDefense > 0)
                {
                    var criticalDefenseMod    = sourcePlayer != null ? 0.05f : 0.25f;
                    var criticalDefenseChance = targetPlayer.AugmentationCriticalDefense * criticalDefenseMod;

                    if (criticalDefenseChance > ThreadSafeRandom.Next(0.0f, 1.0f))
                    {
                        critDefended = true;
                    }
                }

                if (!critDefended)
                {
                    criticalHit = true;
                }
            }

            var absorbMod = GetAbsorbMod(target);

            bool isPVP = sourcePlayer != null && targetPlayer != null;

            if (isPVP && Spell.IsHarmful)
            {
                Player.UpdatePKTimers(sourcePlayer, targetPlayer);
            }

            var elementalDmgBonus = GetCasterElementalDamageModifier(sourceCreature, target, Spell.DamageType);

            // Possible 2x + damage bonus for the slayer property
            var slayerBonus = GetWeaponCreatureSlayerModifier(sourceCreature, target);

            // life magic projectiles: ie., martyr's hecatomb
            if (Spell.School == MagicSchool.LifeMagic)
            {
                var lifeMagicDamage = LifeProjectileDamage * Spell.DamageRatio;

                // could life magic projectiles crit?
                // if so, did they use the same 1.5x formula as war magic, instead of 2.0x?
                if (criticalHit)
                {
                    damageBonus = lifeMagicDamage * 0.5f * GetWeaponCritDamageMod(sourceCreature, attackSkill, target);
                }

                finalDamage = (lifeMagicDamage + damageBonus) * elementalDmgBonus * slayerBonus * absorbMod;
                return(finalDamage);
            }
            // war/void magic projectiles
            else
            {
                if (criticalHit)
                {
                    // Original:
                    // http://acpedia.org/wiki/Announcements_-_2002/08_-_Atonement#Letter_to_the_Players

                    // Critical Strikes: In addition to the skill-based damage bonus, each projectile spell has a 2% chance of causing a critical hit on the target and doing increased damage.
                    // A magical critical hit is similar in some respects to melee critical hits (although the damage calculation is handled differently).
                    // While a melee critical hit automatically does twice the maximum damage of the weapon, a magical critical hit will do an additional half the minimum damage of the spell.
                    // For instance, a magical critical hit from a level 7 spell, which does 110-180 points of damage, would add an additional 55 points of damage to the spell.

                    // Later updated for PvE only:

                    // http://acpedia.org/wiki/Announcements_-_2004/07_-_Treaties_in_Stone#Letter_to_the_Players

                    // Currently when a War Magic spell scores a critical hit, it adds a multiple of the base damage of the spell to a normal damage roll.
                    // Starting in July, War Magic critical hits will instead add a multiple of the maximum damage of the spell.
                    // No more crits that do less damage than non-crits!

                    if (isPVP) // PvP: 50% of the MIN damage added to normal damage roll
                    {
                        damageBonus = Spell.MinDamage * 0.5f;
                    }
                    else   // PvE: 50% of the MAX damage added to normal damage roll
                    {
                        damageBonus = Spell.MaxDamage * 0.5f;
                    }

                    var critDamageMod = GetWeaponCritDamageMod(sourceCreature, attackSkill, target);

                    damageBonus *= critDamageMod;
                }

                /* War Magic skill-based damage bonus
                 * http://acpedia.org/wiki/Announcements_-_2002/08_-_Atonement#Letter_to_the_Players
                 */
                if (sourcePlayer != null)
                {
                    // per retail stats, level 8 difficulty is capped to 350 instead of 400
                    // without this, level 7s have the potential to deal more damage than level 8s
                    var difficulty = Math.Min(Spell.Power, 350);
                    var magicSkill = sourcePlayer.GetCreatureSkill(Spell.School).Current;

                    if (magicSkill > difficulty)
                    {
                        // Bonus clamped to a maximum of 50%
                        //var percentageBonus = Math.Clamp((magicSkill - Spell.Power) / 100.0f, 0.0f, 0.5f);
                        var percentageBonus = (magicSkill - difficulty) / 1000.0f;

                        warSkillBonus = Spell.MinDamage * percentageBonus;
                    }
                }
                var baseDamage = ThreadSafeRandom.Next(Spell.MinDamage, Spell.MaxDamage);

                var weaponResistanceMod = GetWeaponResistanceModifier(sourceCreature, attackSkill, Spell.DamageType);

                var resistanceMod = Math.Max(0.0f, target.GetResistanceMod(resistanceType, null, weaponResistanceMod));

                finalDamage = baseDamage + damageBonus + warSkillBonus;

                finalDamage *= resistanceMod * elementalDmgBonus * slayerBonus * absorbMod;

                return(finalDamage);
            }
        }
예제 #26
0
        public static void UseObjectOnTarget(Player player, WorldObject source, WorldObject target)
        {
            var recipe = DatabaseManager.World.GetCachedCookbook(source.WeenieClassId, target.WeenieClassId);

            if (recipe == null)
            {
                var message = new GameMessageSystemChat($"The {source.Name} cannot be used on the {target.Name}.", ChatMessageType.Craft);
                player.Session.Network.EnqueueSend(message);
                player.SendUseDoneEvent();
                return;
            }

            ActionChain   craftChain     = new ActionChain();
            CreatureSkill skill          = null;
            bool          skillSuccess   = true; // assume success, unless there's a skill check
            double        percentSuccess = 1;

            UniversalMotion motion = new UniversalMotion(MotionStance.Standing, new MotionItem(MotionCommand.ClapHands));

            craftChain.AddAction(player, () => player.HandleActionMotion(motion));
            var motionTable          = DatManager.PortalDat.ReadFromDat <MotionTable>(player.MotionTableId);
            var craftAnimationLength = motionTable.GetAnimationLength(MotionCommand.ClapHands);

            craftChain.AddDelaySeconds(craftAnimationLength);

            craftChain.AddAction(player, () =>
            {
                if (recipe.Recipe.Skill > 0 && recipe.Recipe.Difficulty > 0)
                {
                    // there's a skill associated with this
                    Skill skillId = (Skill)recipe.Recipe.Skill;

                    // this shouldn't happen, but sanity check for unexpected nulls
                    skill = player.GetCreatureSkill(skillId);

                    if (skill == null)
                    {
                        log.Warn("Unexpectedly missing skill in Recipe usage");
                        player.SendUseDoneEvent();
                        return;
                    }

                    percentSuccess = skill.GetPercentSuccess(recipe.Recipe.Difficulty); //FIXME: Pretty certain this is broken
                }

                if (skill != null)
                {
                    if (skill.Status == SkillStatus.Untrained)
                    {
                        var message = new GameEventWeenieError(player.Session, WeenieError.YouAreNotTrainedInThatTradeSkill);
                        player.Session.Network.EnqueueSend(message);
                        player.SendUseDoneEvent(WeenieError.YouAreNotTrainedInThatTradeSkill);
                        return;
                    }
                }

                // straight skill check, if applicable
                if (skill != null)
                {
                    skillSuccess = _random.NextDouble() < percentSuccess;
                }


                var components = recipe.Recipe.RecipeComponent.ToList();

                if (skillSuccess)
                {
                    var targetSuccess = components[0];
                    var sourceSuccess = components[1];

                    bool destroyTarget = _random.NextDouble() < targetSuccess.DestroyChance;
                    bool destroySource = _random.NextDouble() < sourceSuccess.DestroyChance;

                    if (destroyTarget)
                    {
                        if (target.OwnerId == player.Guid.Full)
                        {
                            player.TryRemoveItemFromInventoryWithNetworking(target, (ushort)targetSuccess.DestroyAmount);
                        }
                        else
                        {
                            target.Destroy();
                        }

                        if (targetSuccess.DestroyMessage != "")
                        {
                            var destroyMessage = new GameMessageSystemChat(targetSuccess.DestroyMessage, ChatMessageType.Craft);
                            player.Session.Network.EnqueueSend(destroyMessage);
                        }
                    }

                    if (destroySource)
                    {
                        if (source.OwnerId == player.Guid.Full)
                        {
                            player.TryRemoveItemFromInventoryWithNetworking(source, (ushort)sourceSuccess.DestroyAmount);
                        }
                        else
                        {
                            source.Destroy();
                        }

                        if (sourceSuccess.DestroyMessage != "")
                        {
                            var destroyMessage = new GameMessageSystemChat(sourceSuccess.DestroyMessage, ChatMessageType.Craft);
                            player.Session.Network.EnqueueSend(destroyMessage);
                        }
                    }

                    if (recipe.Recipe.SuccessWCID > 0)
                    {
                        var wo = WorldObjectFactory.CreateNewWorldObject(recipe.Recipe.SuccessWCID);

                        if (wo != null)
                        {
                            if (recipe.Recipe.SuccessAmount > 1)
                            {
                                wo.StackSize = (ushort)recipe.Recipe.SuccessAmount;
                            }

                            player.TryCreateInInventoryWithNetworking(wo);
                        }
                    }

                    var message = new GameMessageSystemChat(recipe.Recipe.SuccessMessage, ChatMessageType.Craft);
                    player.Session.Network.EnqueueSend(message);
                }
                else
                {
                    var targetFail = components[2];
                    var sourceFail = components[3];

                    bool destroyTarget = _random.NextDouble() < targetFail.DestroyChance;
                    bool destroySource = _random.NextDouble() < sourceFail.DestroyChance;

                    if (destroyTarget)
                    {
                        if (target.OwnerId == player.Guid.Full)
                        {
                            player.TryRemoveItemFromInventoryWithNetworking(target, (ushort)targetFail.DestroyAmount);
                        }
                        else
                        {
                            target.Destroy();
                        }

                        if (targetFail.DestroyMessage != "")
                        {
                            var destroyMessage = new GameMessageSystemChat(targetFail.DestroyMessage, ChatMessageType.Craft);
                            player.Session.Network.EnqueueSend(destroyMessage);
                        }
                    }

                    if (destroySource)
                    {
                        if (source.OwnerId == player.Guid.Full)
                        {
                            player.TryRemoveItemFromInventoryWithNetworking(source, (ushort)sourceFail.DestroyAmount);
                        }
                        else
                        {
                            source.Destroy();
                        }

                        if (sourceFail.DestroyMessage != "")
                        {
                            var destroyMessage = new GameMessageSystemChat(sourceFail.DestroyMessage, ChatMessageType.Craft);
                            player.Session.Network.EnqueueSend(destroyMessage);
                        }
                    }

                    if (recipe.Recipe.FailWCID > 0)
                    {
                        var wo = WorldObjectFactory.CreateNewWorldObject(recipe.Recipe.FailWCID);

                        if (wo != null)
                        {
                            if (recipe.Recipe.FailAmount > 1)
                            {
                                wo.StackSize = (ushort)recipe.Recipe.FailAmount;
                            }

                            player.TryCreateInInventoryWithNetworking(wo);
                        }
                    }

                    var message = new GameMessageSystemChat(recipe.Recipe.FailMessage, ChatMessageType.Craft);
                    player.Session.Network.EnqueueSend(message);
                }

                player.SendUseDoneEvent();
            });

            craftChain.EnqueueChain();
        }
예제 #27
0
        /// <summary>
        /// Calculates the damage for a spell projectile
        /// Used by war magic, void magic, and life magic projectiles
        /// </summary>
        public float?CalculateDamage(WorldObject source, WorldObject caster, Creature target, ref bool criticalHit, ref bool critDefended, ref bool overpower)
        {
            var sourcePlayer = source as Player;
            var targetPlayer = target as Player;

            if (source == null || !target.IsAlive || targetPlayer != null && targetPlayer.Invincible)
            {
                return(null);
            }

            // check lifestone protection
            if (targetPlayer != null && targetPlayer.UnderLifestoneProtection)
            {
                if (sourcePlayer != null)
                {
                    sourcePlayer.Session.Network.EnqueueSend(new GameMessageSystemChat($"The Lifestone's magic protects {targetPlayer.Name} from the attack!", ChatMessageType.Magic));
                }

                targetPlayer.HandleLifestoneProtection();
                return(null);
            }

            var critDamageBonus     = 0.0f;
            var weaponCritDamageMod = 1.0f;
            var weaponResistanceMod = 1.0f;
            var resistanceMod       = 1.0f;

            // life magic
            var lifeMagicDamage = 0.0f;

            // war/void magic
            var baseDamage  = 0;
            var skillBonus  = 0.0f;
            var finalDamage = 0.0f;

            var resistanceType = Creature.GetResistanceType(Spell.DamageType);

            var sourceCreature = source as Creature;

            if (sourceCreature?.Overpower != null)
            {
                overpower = Creature.GetOverpower(sourceCreature, target);
            }

            var resisted = source.TryResistSpell(target, Spell, caster, true);

            if (resisted && !overpower)
            {
                return(null);
            }

            CreatureSkill attackSkill = null;

            if (sourceCreature != null)
            {
                attackSkill = sourceCreature.GetCreatureSkill(Spell.School);
            }

            // critical hit
            var criticalChance = GetWeaponMagicCritFrequency(sourceCreature, attackSkill, target);

            if (ThreadSafeRandom.Next(0.0f, 1.0f) < criticalChance)
            {
                if (targetPlayer != null && targetPlayer.AugmentationCriticalDefense > 0)
                {
                    var criticalDefenseMod    = sourcePlayer != null ? 0.05f : 0.25f;
                    var criticalDefenseChance = targetPlayer.AugmentationCriticalDefense * criticalDefenseMod;

                    if (criticalDefenseChance > ThreadSafeRandom.Next(0.0f, 1.0f))
                    {
                        critDefended = true;
                    }
                }

                if (!critDefended)
                {
                    criticalHit = true;
                }
            }

            var absorbMod = GetAbsorbMod(target);

            bool isPVP = sourcePlayer != null && targetPlayer != null;

            if (isPVP && Spell.IsHarmful)
            {
                Player.UpdatePKTimers(sourcePlayer, targetPlayer);
            }

            var elementalDamageMod = GetCasterElementalDamageModifier(sourceCreature, target, Spell.DamageType);

            // Possible 2x + damage bonus for the slayer property
            var slayerMod = GetWeaponCreatureSlayerModifier(sourceCreature, target);

            // life magic projectiles: ie., martyr's hecatomb
            if (Spell.MetaSpellType == ACE.Entity.Enum.SpellType.LifeProjectile)
            {
                lifeMagicDamage = LifeProjectileDamage * Spell.DamageRatio;

                // could life magic projectiles crit?
                // if so, did they use the same 1.5x formula as war magic, instead of 2.0x?
                if (criticalHit)
                {
                    weaponCritDamageMod = GetWeaponCritDamageMod(sourceCreature, attackSkill, target);
                    critDamageBonus     = lifeMagicDamage * 0.5f * weaponCritDamageMod;
                }

                weaponResistanceMod = GetWeaponResistanceModifier(sourceCreature, attackSkill, Spell.DamageType);

                // if attacker/weapon has IgnoreMagicResist directly, do not transfer to spell projectile
                // only pass if SpellProjectile has it directly, such as 2637 - Invoking Aun Tanua

                resistanceMod = (float)Math.Max(0.0f, target.GetResistanceMod(resistanceType, this, null, weaponResistanceMod));

                finalDamage = (lifeMagicDamage + critDamageBonus) * elementalDamageMod * slayerMod * resistanceMod * absorbMod;
            }
            // war/void magic projectiles
            else
            {
                if (criticalHit)
                {
                    // Original:
                    // http://acpedia.org/wiki/Announcements_-_2002/08_-_Atonement#Letter_to_the_Players

                    // Critical Strikes: In addition to the skill-based damage bonus, each projectile spell has a 2% chance of causing a critical hit on the target and doing increased damage.
                    // A magical critical hit is similar in some respects to melee critical hits (although the damage calculation is handled differently).
                    // While a melee critical hit automatically does twice the maximum damage of the weapon, a magical critical hit will do an additional half the minimum damage of the spell.
                    // For instance, a magical critical hit from a level 7 spell, which does 110-180 points of damage, would add an additional 55 points of damage to the spell.

                    // Later updated for PvE only:

                    // http://acpedia.org/wiki/Announcements_-_2004/07_-_Treaties_in_Stone#Letter_to_the_Players

                    // Currently when a War Magic spell scores a critical hit, it adds a multiple of the base damage of the spell to a normal damage roll.
                    // Starting in July, War Magic critical hits will instead add a multiple of the maximum damage of the spell.
                    // No more crits that do less damage than non-crits!

                    if (isPVP) // PvP: 50% of the MIN damage added to normal damage roll
                    {
                        critDamageBonus = Spell.MinDamage * 0.5f;
                    }
                    else   // PvE: 50% of the MAX damage added to normal damage roll
                    {
                        critDamageBonus = Spell.MaxDamage * 0.5f;
                    }

                    weaponCritDamageMod = GetWeaponCritDamageMod(sourceCreature, attackSkill, target);

                    critDamageBonus *= weaponCritDamageMod;
                }

                /* War Magic skill-based damage bonus
                 * http://acpedia.org/wiki/Announcements_-_2002/08_-_Atonement#Letter_to_the_Players
                 */
                if (sourcePlayer != null)
                {
                    // per retail stats, level 8 difficulty is capped to 350 instead of 400
                    // without this, level 7s have the potential to deal more damage than level 8s
                    var difficulty = Math.Min(Spell.Power, 350);    // was skillMod possibility capped to 1.3x for level 7 spells in retail, instead of level 8 difficulty cap?
                    var magicSkill = sourcePlayer.GetCreatureSkill(Spell.School).Current;

                    if (magicSkill > difficulty)
                    {
                        // Bonus clamped to a maximum of 50%
                        //var percentageBonus = Math.Clamp((magicSkill - Spell.Power) / 100.0f, 0.0f, 0.5f);
                        var percentageBonus = (magicSkill - difficulty) / 1000.0f;

                        skillBonus = Spell.MinDamage * percentageBonus;
                    }
                }
                baseDamage = ThreadSafeRandom.Next(Spell.MinDamage, Spell.MaxDamage);

                weaponResistanceMod = GetWeaponResistanceModifier(sourceCreature, attackSkill, Spell.DamageType);

                // if attacker/weapon has IgnoreMagicResist directly, do not transfer to spell projectile
                // only pass if SpellProjectile has it directly, such as 2637 - Invoking Aun Tanua

                resistanceMod = (float)Math.Max(0.0f, target.GetResistanceMod(resistanceType, this, null, weaponResistanceMod));

                if (sourcePlayer != null && targetPlayer != null && Spell.DamageType == DamageType.Nether)
                {
                    // for direct damage from void spells in pvp,
                    // apply void_pvp_modifier *on top of* the player's natural resistance to nether

                    // this supposedly brings the direct damage from void spells in pvp closer to retail
                    resistanceMod *= (float)PropertyManager.GetDouble("void_pvp_modifier").Item;
                }

                finalDamage = baseDamage + critDamageBonus + skillBonus;

                finalDamage *= elementalDamageMod * slayerMod * resistanceMod * absorbMod;
            }

            // show debug info
            if (sourceCreature != null && sourceCreature.DebugDamage.HasFlag(Creature.DebugDamageType.Attacker))
            {
                ShowInfo(sourceCreature, Spell, attackSkill, criticalChance, criticalHit, critDefended, overpower, weaponCritDamageMod, skillBonus, baseDamage, critDamageBonus, elementalDamageMod, slayerMod, weaponResistanceMod, resistanceMod, absorbMod, LifeProjectileDamage, lifeMagicDamage, finalDamage);
            }
            if (target.DebugDamage.HasFlag(Creature.DebugDamageType.Defender))
            {
                ShowInfo(target, Spell, attackSkill, criticalChance, criticalHit, critDefended, overpower, weaponCritDamageMod, skillBonus, baseDamage, critDamageBonus, elementalDamageMod, slayerMod, weaponResistanceMod, resistanceMod, absorbMod, LifeProjectileDamage, lifeMagicDamage, finalDamage);
            }
            return(finalDamage);
        }
예제 #28
0
        public static void OnSuccessUse(Player player, CreatureSkill skill, uint difficulty)
        {
            //Console.WriteLine($"Proficiency.OnSuccessUse({player.Name}, {skill.Skill}, targetDiff: {difficulty})");

            // TODO: this formula still probably needs some work to match up with retail truly...

            // possible todo: does this only apply to players?
            // ie., can monsters still level up from skill usage, or killing players?
            // it was possible on release, but i think they might have removed that feature?

            // ensure skill is at least trained
            if (skill.AdvancementClass < SkillAdvancementClass.Trained)
            {
                return;
            }

            var last_difficulty = skill.PropertiesSkill.ResistanceAtLastCheck;
            var last_used_time  = skill.PropertiesSkill.LastUsedTime;

            var currentTime = Time.GetUnixTime();

            var timeDiff = currentTime - last_used_time;

            var difficulty_check = difficulty > last_difficulty;
            var time_check       = timeDiff >= FullTime.TotalSeconds;

            if (difficulty_check || time_check)
            {
                // todo: not independent variables?
                // always scale if timeDiff < FullTime?
                var timeScale = 1.0f;
                if (!time_check)
                {
                    // 10 mins elapsed from 15 min FullTime:
                    // 0.66f timeScale
                    timeScale = (float)(timeDiff / FullTime.TotalSeconds);

                    // any rng involved?
                }

                skill.PropertiesSkill.ResistanceAtLastCheck = difficulty;
                skill.PropertiesSkill.LastUsedTime          = currentTime;

                player.ChangesDetected = true;

                if (player.IsMaxLevel)
                {
                    return;
                }

                var pp             = (uint)Math.Round(difficulty * timeScale);
                var totalXPGranted = (long)Math.Round(pp * 1.1f);   // give additional 10% of proficiency XP to unassigned XP

                if (totalXPGranted > 10000)
                {
                    log.Warn($"Proficiency.OnSuccessUse({player.Name}, {skill.Skill}, {difficulty})");
                }

                var maxLevel    = Player.GetMaxLevel();
                var remainingXP = player.GetRemainingXP(maxLevel).Value;

                if (totalXPGranted > remainingXP)
                {
                    // checks and balances:
                    // total xp = pp * 1.1
                    // pp = total xp / 1.1

                    totalXPGranted = remainingXP;
                    pp             = (uint)Math.Round(totalXPGranted / 1.1f);
                }

                // if skill is maxed out, but player is below MaxLevel,
                // not sure if retail granted 0%, 10%, or 110% of the pp to TotalExperience here
                // since pp is such a miniscule system at the higher levels,
                // going to just naturally add it to TotalXP for now..

                pp = Math.Min(pp, skill.ExperienceLeft);

                //Console.WriteLine($"Earned {pp} PP ({skill.Skill})");

                // send CP to player as unassigned XP
                player.GrantXP(totalXPGranted, XpType.Proficiency, ShareType.None);

                // send PP to player as skill XP, which gets spent from the CP sent
                if (pp > 0)
                {
                    player.HandleActionRaiseSkill(skill.Skill, pp);
                }
            }
        }
예제 #29
0
        private static void HandleHealingRecipe(Player player, WorldObject source, WorldObject target, Recipe recipe)
        {
            ActionChain chain = new ActionChain();

            // skill will be null since the difficulty is calculated manually
            if (recipe.SkillId == null)
            {
                log.Warn($"healing recipe has null skill id (should almost certainly be healing, but who knows).  recipe id {recipe.RecipeId}.");
                player.SendUseDoneEvent();
                return;
            }

            if (!(target is Player))
            {
                var message = new GameMessageSystemChat($"The {source.Name} cannot be used on {target.Name}.", ChatMessageType.Craft);
                player.Session.Network.EnqueueSend(message);
                player.SendUseDoneEvent();
                return;
            }

            Player  targetPlayer = target as Player;
            Ability vital        = (Ability?)recipe.HealingAttribute ?? Ability.Health;

            // there's a skill associated with this
            Skill skillId = (Skill)recipe.SkillId.Value;

            // this shouldn't happen, but sanity check for unexpected nulls
            if (!player.Skills.ContainsKey(skillId))
            {
                log.Warn("Unexpectedly missing skill in Recipe usage");
                player.SendUseDoneEvent();
                return;
            }

            CreatureSkill skill = player.Skills[skillId];

            // at this point, we've validated that the target is a player, and the target is below max health

            if (target.Guid != player.Guid)
            {
                // TODO: validate range
            }

            MotionCommand cmd = MotionCommand.SkillHealSelf;

            if (target.Guid != player.Guid)
            {
                cmd = MotionCommand.Woah; // guess?  nothing else stood out
            }
            // everything pre-validatable is validated.  action will be attempted unless cancelled, so
            // queue up the animation and action
            UniversalMotion motion = new UniversalMotion(MotionStance.Standing, new MotionItem(cmd));

            chain.AddAction(player, () => player.HandleActionMotion(motion));
            chain.AddDelaySeconds(0.5);

            chain.AddAction(player, () =>
            {
                // TODO: revalidate range if other player (they could have moved)

                double difficulty = 2 * (targetPlayer.Vitals[vital].MaxValue - targetPlayer.Vitals[vital].Current);
                if (difficulty <= 0)
                {
                    // target is at max (or higher?) health, do nothing
                    var text = "You are already at full health.";

                    if (target.Guid != player.Guid)
                    {
                        text = $"{target.Name} is already at full health";
                    }

                    player.Session.Network.EnqueueSend(new GameMessageSystemChat(text, ChatMessageType.Craft));
                    player.SendUseDoneEvent();
                    return;
                }

                if (player.CombatMode != CombatMode.NonCombat && player.CombatMode != CombatMode.Undef)
                {
                    difficulty *= 1.1;
                }

                uint boost        = source.Boost ?? 0;
                double multiplier = source.HealkitMod ?? 1;

                double playerSkill = skill.ActiveValue + boost;
                if (skill.Status == SkillStatus.Trained)
                {
                    playerSkill *= 1.1;
                }
                else if (skill.Status == SkillStatus.Specialized)
                {
                    playerSkill *= 1.5;
                }

                // usage is inevitable at this point, consume the use
                if ((recipe.ResultFlags & (uint)RecipeResult.SourceItemUsesDecrement) > 0)
                {
                    if (source.Structure <= 1)
                    {
                        player.DestroyInventoryItem(source);
                    }
                    else
                    {
                        source.Structure--;
                        source.SendPartialUpdates(player.Session, _updateStructure);
                    }
                }

                double percentSuccess = CreatureSkill.GetPercentSuccess((uint)playerSkill, (uint)difficulty);

                if (_random.NextDouble() <= percentSuccess)
                {
                    string expertly = "";

                    if (_random.NextDouble() < 0.1d)
                    {
                        expertly    = "expertly ";
                        multiplier *= 1.2;
                    }

                    // calculate amount restored
                    uint maxRestore = targetPlayer.Vitals[vital].MaxValue - targetPlayer.Vitals[vital].Current;

                    // TODO: get actual forumula for healing.  this is COMPLETELY wrong.  this is 60 + random(1-60).
                    double amountRestored = 60d + _random.Next(1, 61);
                    amountRestored       *= multiplier;

                    uint actualRestored = (uint)Math.Min(maxRestore, amountRestored);
                    targetPlayer.Vitals[vital].Current += actualRestored;

                    var updateVital = new GameMessagePrivateUpdateAttribute2ndLevel(player.Session, vital.GetVital(), targetPlayer.Vitals[vital].Current);
                    player.Session.Network.EnqueueSend(updateVital);

                    if (targetPlayer.Guid != player.Guid)
                    {
                        // tell the other player they got healed
                        var updateVitalToTarget = new GameMessagePrivateUpdateAttribute2ndLevel(targetPlayer.Session, vital.GetVital(), targetPlayer.Vitals[vital].Current);
                        targetPlayer.Session.Network.EnqueueSend(updateVitalToTarget);
                    }

                    string name = "yourself";
                    if (targetPlayer.Guid != player.Guid)
                    {
                        name = targetPlayer.Name;
                    }

                    string vitalName = "Health";

                    if (vital == Ability.Stamina)
                    {
                        vitalName = "Stamina";
                    }
                    else if (vital == Ability.Mana)
                    {
                        vitalName = "Mana";
                    }

                    string uses = source.Structure == 1 ? "use" : "uses";

                    var text    = string.Format(recipe.SuccessMessage, expertly, name, actualRestored, vitalName, source.Name, source.Structure, uses);
                    var message = new GameMessageSystemChat(text, ChatMessageType.Craft);
                    player.Session.Network.EnqueueSend(message);

                    if (targetPlayer.Guid != player.Guid)
                    {
                        // send text to the other player too
                        text    = string.Format(recipe.AlternateMessage, player.Name, expertly, actualRestored, vitalName);
                        message = new GameMessageSystemChat(text, ChatMessageType.Craft);
                        targetPlayer.Session.Network.EnqueueSend(message);
                    }
                }

                player.SendUseDoneEvent();
            });

            chain.EnqueueChain();
        }
예제 #30
0
        private static void HandleCreateItemRecipe(Player player, WorldObject source, WorldObject target, Recipe recipe)
        {
            ActionChain   craftChain     = new ActionChain();
            CreatureSkill skill          = null;
            bool          skillSuccess   = true; // assume success, unless there's a skill check
            double        percentSuccess = 1;

            UniversalMotion motion = new UniversalMotion(MotionStance.Standing, new MotionItem(MotionCommand.ClapHands));

            craftChain.AddAction(player, () => player.HandleActionMotion(motion));
            float craftAnimationLength = MotionTable.GetAnimationLength((uint)player.MotionTableId, MotionCommand.ClapHands);

            craftChain.AddDelaySeconds(craftAnimationLength);
            // craftChain.AddDelaySeconds(0.5);

            craftChain.AddAction(player, () =>
            {
                if (recipe.SkillId != null && recipe.SkillDifficulty != null)
                {
                    // there's a skill associated with this
                    Skill skillId = (Skill)recipe.SkillId.Value;

                    // this shouldn't happen, but sanity check for unexpected nulls
                    if (!player.Skills.ContainsKey(skillId))
                    {
                        log.Warn("Unexpectedly missing skill in Recipe usage");
                        player.SendUseDoneEvent();
                        return;
                    }

                    skill          = player.Skills[skillId];
                    percentSuccess = skill.GetPercentSuccess(recipe.SkillDifficulty.Value);
                }

                // straight skill check, if applciable
                if (skill != null)
                {
                    skillSuccess = _random.NextDouble() < percentSuccess;
                }

                if ((recipe.ResultFlags & (uint)RecipeResult.SourceItemDestroyed) > 0)
                {
                    player.DestroyInventoryItem(source);
                }

                if ((recipe.ResultFlags & (uint)RecipeResult.TargetItemDestroyed) > 0)
                {
                    player.DestroyInventoryItem(target);
                }

                if ((recipe.ResultFlags & (uint)RecipeResult.SourceItemUsesDecrement) > 0)
                {
                    if (source.Structure <= 1)
                    {
                        player.DestroyInventoryItem(source);
                    }
                    else
                    {
                        source.Structure--;
                        source.SendPartialUpdates(player.Session, _updateStructure);
                    }
                }

                if ((recipe.ResultFlags & (uint)RecipeResult.TargetItemUsesDecrement) > 0)
                {
                    if (target.Structure <= 1)
                    {
                        player.DestroyInventoryItem(target);
                    }
                    else
                    {
                        target.Structure--;
                        target.SendPartialUpdates(player.Session, _updateStructure);
                    }
                }

                if (skillSuccess)
                {
                    WorldObject newObject1 = null;
                    WorldObject newObject2 = null;

                    if ((recipe.ResultFlags & (uint)RecipeResult.SuccessItem1) > 0 && recipe.SuccessItem1Wcid != null)
                    {
                        newObject1 = player.AddNewItemToInventory(recipe.SuccessItem1Wcid.Value);
                    }

                    if ((recipe.ResultFlags & (uint)RecipeResult.SuccessItem2) > 0 && recipe.SuccessItem2Wcid != null)
                    {
                        newObject2 = player.AddNewItemToInventory(recipe.SuccessItem2Wcid.Value);
                    }

                    var text    = string.Format(recipe.SuccessMessage, source.Name, target.Name, newObject1?.Name, newObject2?.Name);
                    var message = new GameMessageSystemChat(text, ChatMessageType.Craft);
                    player.Session.Network.EnqueueSend(message);
                }
                else
                {
                    WorldObject newObject1 = null;
                    WorldObject newObject2 = null;

                    if ((recipe.ResultFlags & (uint)RecipeResult.FailureItem1) > 0 && recipe.FailureItem1Wcid != null)
                    {
                        newObject1 = player.AddNewItemToInventory(recipe.FailureItem1Wcid.Value);
                    }

                    if ((recipe.ResultFlags & (uint)RecipeResult.FailureItem2) > 0 && recipe.FailureItem2Wcid != null)
                    {
                        newObject2 = player.AddNewItemToInventory(recipe.FailureItem2Wcid.Value);
                    }

                    var text    = string.Format(recipe.FailMessage, source.Name, target.Name, newObject1?.Name, newObject2?.Name);
                    var message = new GameMessageSystemChat(text, ChatMessageType.Craft);
                    player.Session.Network.EnqueueSend(message);
                }

                player.SendUseDoneEvent();
            });

            craftChain.EnqueueChain();
        }