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; } } }
/// <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); }
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; }
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); }
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); }
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); } }
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]); } }
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"); } }
public void SpendAllAvailableSkillXp(CreatureSkill creatureSkill, bool sendNetworkUpdate = true) { var amountRemaining = creatureSkill.ExperienceLeft; if (amountRemaining > AvailableExperience) { amountRemaining = (uint)AvailableExperience; } SpendSkillXp(creatureSkill, amountRemaining, sendNetworkUpdate); }
/// <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); }
/// <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); } }
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); }
/// <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); }
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)); } }
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)); }
/* * 该方法逻辑较为复杂 * 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); } } }
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); }
/// <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); }
/// <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]); }
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); }
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; }
/// <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]); }
/// <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); }
/// <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); } }
/// <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); } }
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(); }
/// <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); }
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); } } }
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(); }
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(); }