public void SendMessage(string msg, ChatMessageType type = ChatMessageType.Broadcast, WorldObject source = null) { if (SquelchManager.IsLegalChannel(type) && SquelchManager.Squelches.Contains(source, type)) { return; } Session.Network.EnqueueSend(new GameMessageSystemChat(msg, type)); }
/// <summary> /// Called when a player runs over the pressure plate /// </summary> public override void OnCollideObject(WorldObject wo) { OnActivate(wo); }
public override void ActOnUse(WorldObject activator) { // handled in base.OnActivate -> EmoteManager.OnUse() }
/// <summary> /// Return the scalar damage absorbed by a shield /// </summary> public float GetShieldMod(WorldObject attacker, DamageType damageType, WorldObject weapon) { // ensure combat stance if (CombatMode == CombatMode.NonCombat) { return(1.0f); } // does the player have a shield equipped? var shield = GetEquippedShield(); if (shield == null) { return(1.0f); } // phantom weapons ignore all armor and shields if (weapon != null && weapon.HasImbuedEffect(ImbuedEffectType.IgnoreAllArmor)) { return(1.0f); } // is monster in front of player, // within shield effectiveness area? var effectiveAngle = 180.0f; var angle = GetAngle(attacker); if (Math.Abs(angle) > effectiveAngle / 2.0f) { return(1.0f); } // get base shield AL var baseSL = shield.GetProperty(PropertyInt.ArmorLevel) ?? 0.0f; // shield AL item enchantment additives: // impenetrability, brittlemail var ignoreMagicArmor = (weapon?.IgnoreMagicArmor ?? false) || (attacker?.IgnoreMagicArmor ?? false); var modSL = shield.EnchantmentManager.GetArmorMod(); if (ignoreMagicArmor) { modSL = attacker is Player ? (int)Math.Round(IgnoreMagicArmorScaled(modSL)) : 0; } var effectiveSL = baseSL + modSL; // get shield RL against damage type var baseRL = GetResistance(shield, damageType); // shield RL item enchantment additives: // banes, lures var modRL = shield.EnchantmentManager.GetArmorModVsType(damageType); if (ignoreMagicArmor) { modRL = attacker is Player?IgnoreMagicArmorScaled(modRL) : 0.0f; } var effectiveRL = (float)(baseRL + modRL); // resistance clamp effectiveRL = Math.Clamp(effectiveRL, -2.0f, 2.0f); // handle negative SL if (effectiveSL < 0) { effectiveRL = 1.0f / effectiveRL; } var effectiveLevel = effectiveSL * effectiveRL; // SL cap: // Trained / untrained: 1/2 shield skill // Spec: shield skill // SL cap is applied *after* item enchantments var shieldSkill = GetCreatureSkill(Skill.Shield); var shieldCap = shieldSkill.Current; if (shieldSkill.AdvancementClass != SkillAdvancementClass.Specialized) { shieldCap = (uint)Math.Round(shieldCap / 2.0f); } effectiveLevel = Math.Min(effectiveLevel, shieldCap); var ignoreShieldMod = attacker.GetIgnoreShieldMod(weapon); //Console.WriteLine($"IgnoreShieldMod: {ignoreShieldMod}"); effectiveLevel *= ignoreShieldMod; // SL is multiplied by existing AL var shieldMod = SkillFormula.CalcArmorMod(effectiveLevel); //Console.WriteLine("ShieldMod: " + shieldMod); return(shieldMod); }
public void FightDirty(WorldObject target) { // Skill description: // Your melee and missile attacks have a chance to weaken your opponent. // - Low attacks can reduce the defense skills of the opponent. // - Medium attacks can cause small amounts of bleeding damage. // - High attacks can reduce opponents' attack and healing skills // Effects: // Low: reduces the defense skills of the opponent by -10 // Medium: bleed ticks for 60 damage per 20 seconds // High: reduces the attack skills of the opponent by -10, and // the healing effects of the opponent by -15 rating // // these damage #s are doubled for dirty fighting specialized. // Notes: // - Dirty fighting works for melee and missile attacks. // - Has a 25% chance to activate on any melee of missile attack. // - This activation is reduced proportionally if Dirty Fighting is lower // than your active weapon skill as determined by your equipped weapon. // - All activate effects last 20 seconds. // - Although a specific effect won't stack with itself, // you can stack all 3 effects on the opponent at the same time. This means // when a skill activates at one attack height, you can move to another attack height // to try to land an additional effect. // - Successfully landing a Dirty Fighting effect is mentioned in chat. Additionally, // the medium height effect results in 'floating glyphs' around the target: // "Dirty Fighting! <Player> delivers a Bleeding Assault to <target>!" // "Dirty Fighting! <Player> delivers a Traumatic Assault to <target>!" // dirty fighting skill must be at least trained var dirtySkill = GetCreatureSkill(Skill.DirtyFighting); if (dirtySkill.AdvancementClass < SkillAdvancementClass.Trained) { return; } // ensure creature target var creatureTarget = target as Creature; if (creatureTarget == null) { return; } var chance = 0.25f; var attackSkill = GetCreatureSkill(GetCurrentWeaponSkill()); if (dirtySkill.Current < attackSkill.Current) { chance *= (float)dirtySkill.Current / attackSkill.Current; } var rng = ThreadSafeRandom.Next(0.0f, 1.0f); if (rng > chance) { return; } switch (AttackHeight) { case ACE.Entity.Enum.AttackHeight.Low: FightDirty_ApplyLowAttack(creatureTarget); break; case ACE.Entity.Enum.AttackHeight.Medium: FightDirty_ApplyMediumAttack(creatureTarget); break; case ACE.Entity.Enum.AttackHeight.High: FightDirty_ApplyHighAttack(creatureTarget); break; } }
/// <summary> /// Returns a value between 0.6-1.6 for bow attacks, /// depending on the accuracy meter /// </summary> public virtual float GetAccuracyMod(WorldObject weapon) { // doesn't apply for non-player creatures? return(1.0f); }
/// <summary> /// Called when a creature hits a target /// </summary> public virtual void OnDamageTarget(WorldObject target, CombatType attackType, bool critical) { // empty base for non-player creatures? }
/// <summary> /// Return the scalar damage absorbed by a shield /// </summary> public float GetShieldMod(WorldObject attacker, DamageType damageType) { // does the player have a shield equipped? var shield = GetEquippedShield(); if (shield == null) { return(1.0f); } // is monster in front of player, // within shield effectiveness area? var effectiveAngle = 180.0f; var angle = GetAngle(attacker); if (Math.Abs(angle) > effectiveAngle / 2.0f) { return(1.0f); } // get base shield AL var baseSL = shield.GetProperty(PropertyInt.ArmorLevel) ?? 0.0f; // shield AL item enchantment additives: // impenetrability, brittlemail var modSL = shield.EnchantmentManager.GetArmorMod(); var effectiveSL = baseSL + modSL; // get shield RL against damage type var baseRL = GetResistance(shield, damageType); // shield RL item enchantment additives: // banes, lures var modRL = shield.EnchantmentManager.GetArmorModVsType(damageType); var effectiveRL = (float)(baseRL + modRL); // resistance cap if (effectiveRL > 2.0f) { effectiveRL = 2.0f; } var effectiveLevel = effectiveSL * effectiveRL; // SL cap: // Trained / untrained: 1/2 shield skill // Spec: shield skill // SL cap is applied *after* item enchantments var shieldSkill = GetCreatureSkill(Skill.Shield); var shieldCap = shieldSkill.Current; if (shieldSkill.AdvancementClass != SkillAdvancementClass.Specialized) { shieldCap = (uint)Math.Round(shieldCap / 2.0f); } effectiveLevel = Math.Min(effectiveLevel, shieldCap); // SL is multiplied by existing AL var shieldMod = SkillFormula.CalcArmorMod(effectiveLevel); //Console.WriteLine("ShieldMod: " + shieldMod); return(shieldMod); }
/// <summary> /// This method sets properties needed for items that will be child items. /// Items here are only items equipped in the hands. /// This deals with the orientation and positioning for visual appearance of the child items held by the parent. Og II /// </summary> /// <param name="item">The child item - we link them together</param> /// <param name="placementPosition">Where is this on the parent - where is it equipped</param> /// <param name="placementId">out parameter - this deals with the orientation of the child item as it relates to parent model</param> /// <param name="parentLocation">out parameter - this is another part of the orientation data for correct visual display</param> protected void SetChild(WorldObject item, int placementPosition, out int placementId, out int parentLocation) { placementId = 0; parentLocation = 0; // TODO: I think there is a state missing - it is one of the edge cases. I need to revist this. Og II switch ((EquipMask)placementPosition) { case EquipMask.MeleeWeapon: placementId = (int)ACE.Entity.Enum.Placement.RightHandCombat; parentLocation = (int)ACE.Entity.Enum.ParentLocation.RightHand; break; case EquipMask.Shield: if (item.ItemType == ItemType.Armor) { placementId = (int)ACE.Entity.Enum.Placement.Shield; parentLocation = (int)ACE.Entity.Enum.ParentLocation.Shield; } else { placementId = (int)ACE.Entity.Enum.Placement.RightHandCombat; parentLocation = (int)ACE.Entity.Enum.ParentLocation.LeftWeapon; } break; case EquipMask.MissileWeapon: if (item.DefaultCombatStyle == CombatStyle.Bow || item.DefaultCombatStyle == CombatStyle.Crossbow) { placementId = (int)ACE.Entity.Enum.Placement.LeftHand; parentLocation = (int)ACE.Entity.Enum.ParentLocation.LeftHand; } else { placementId = (int)ACE.Entity.Enum.Placement.RightHandCombat; parentLocation = (int)ACE.Entity.Enum.ParentLocation.RightHand; } break; case EquipMask.MissileAmmo: throw new NotImplementedException(); break; case EquipMask.Held: placementId = (int)ACE.Entity.Enum.Placement.RightHandCombat; parentLocation = (int)ACE.Entity.Enum.ParentLocation.RightHand; break; default: placementId = (int)ACE.Entity.Enum.Placement.Default; parentLocation = (int)ACE.Entity.Enum.ParentLocation.None; break; } if (item.CurrentWieldedLocation != null) { Children.Add(new HeldItem(item.Guid.Full, parentLocation, (EquipMask)item.CurrentWieldedLocation)); } item.Placement = (Placement)placementId; item.ParentLocation = (ParentLocation)parentLocation; item.Location = Location; }
/// <summary> /// Creates an enchantment and interacts with the Enchantment registry. /// Used by Life, Creature, Item, and Void magic /// </summary> /// <param name="target"></param> /// <param name="spell"></param> /// <param name="spellStatMod"></param> /// <param name="castByItem"></param> /// <returns></returns> private string CreateEnchantment(WorldObject target, SpellBase spell, Database.Models.World.Spell spellStatMod, string castByItem = null) { double duration; if (castByItem == null) { duration = -1; } else { duration = spell.Duration; } // create enchantment var enchantment = new Enchantment(target, spellStatMod.SpellId, duration, 1, (uint)EnchantmentMask.CreatureSpells); var stackType = target.EnchantmentManager.Add(enchantment, castByItem); var player = this as Player; var playerTarget = target as Player; var creatureTarget = target as Creature; // build message var suffix = ""; switch (stackType) { case StackType.Refresh: suffix = $", refreshing {spell.Name}"; break; case StackType.Surpass: suffix = $", surpassing {target.EnchantmentManager.Surpass.Name}"; break; case StackType.Surpassed: suffix = $", but it is surpassed by {target.EnchantmentManager.Surpass.Name}"; break; } var targetName = this == target ? "yourself" : target.Name; string message; if (castByItem != null) { message = $"{castByItem} casts {spell.Name} on you"; } else { message = $"You cast {spell.Name} on {targetName}{suffix}"; } if (target is Player) { if (stackType != StackType.Surpassed) { playerTarget.Session.Network.EnqueueSend(new GameEventMagicUpdateEnchantment(playerTarget.Session, enchantment)); } if (playerTarget != this) { playerTarget.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} cast {spell.Name} on you{suffix}", ChatMessageType.Magic)); } } return(message); }
/// <summary> /// Creates the Magic projectile spells for Life, War, and Void Magic /// </summary> /// <param name="caster"></param> /// <param name="target"></param> /// <param name="spellId"></param> /// <param name="projectileWcid"></param> /// <param name="lifeProjectileDamage"></param> private void CreateSpellProjectile(WorldObject caster, WorldObject target, uint spellId, uint projectileWcid, uint lifeProjectileDamage = 0) { SpellProjectile spellProjectile = WorldObjectFactory.CreateNewWorldObject(projectileWcid) as SpellProjectile; spellProjectile.Setup(spellId); var origin = caster.Location.ToGlobal(); if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Arc) { origin.Z += caster.Height; } else { origin.Z += caster.Height * 2.0f / 3.0f; } var dest = target.Location.ToGlobal(); dest.Z += target.Height / 2.0f; var direction = Vector3.Normalize(dest - origin); // This is not perfect but is close to values that retail used. TODO: revisit this later. origin += direction * (caster.PhysicsObj.GetRadius() + spellProjectile.PhysicsObj.GetRadius()); float time; var dist = (dest - origin).Length(); float speed = 15f; if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Bolt) { speed = GetStationaryVelocity(15f, dist); } else if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Streak) { speed = GetStationaryVelocity(45f, dist); } else if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Arc) { speed = GetStationaryVelocity(40f, dist); } // TODO: Implement target leading for non arc spells // Also: velocity seems to increase when target is moving away from the caster and decrease when // the target is moving toward the caster. This still needs more research. var velocity = direction * speed; var useGravity = spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Arc; spellProjectile.Velocity = GetSpellProjectileVelocity(origin, target, dest, speed, useGravity, out time); spellProjectile.FlightTime = time; var loc = caster.Location; origin = loc.Pos; if (spellProjectile.SpellType == SpellProjectile.ProjectileSpellType.Arc) { origin.Z += caster.Height; } else { origin.Z += caster.Height * 2.0f / 3.0f; } origin += direction * (caster.PhysicsObj.GetRadius() + spellProjectile.PhysicsObj.GetRadius()); spellProjectile.Location = new ACE.Entity.Position(loc.LandblockId.Raw, origin.X, origin.Y, origin.Z, loc.Rotation.X, loc.Rotation.Y, loc.Rotation.Z, loc.RotationW); spellProjectile.ParentWorldObject = (Creature)this; spellProjectile.TargetGuid = target.Guid; spellProjectile.LifeProjectileDamage = lifeProjectileDamage; spellProjectile.ProjectileSource = caster; spellProjectile.ProjectileTarget = target; spellProjectile.SetProjectilePhysicsState(target, useGravity); LandblockManager.AddObject(spellProjectile); CurrentLandblock.EnqueueBroadcast(spellProjectile.Location, new GameMessageScript(spellProjectile.Guid, ACE.Entity.Enum.PlayScript.Launch, spellProjectile.PlayscriptIntensity)); // detonate point-blank projectiles immediately var radsum = target.PhysicsObj.GetRadius() + spellProjectile.PhysicsObj.GetRadius(); if (dist < radsum) { spellProjectile.OnCollideObject(target); } // TODO : removed when real server projectile tracking and collisions are implemented /*var actionChain = new ActionChain(); * actionChain.AddDelaySeconds(spellProjectile.FlightTime); * actionChain.AddAction(spellProjectile, () => spellProjectile.HandleOnCollide(spellProjectile.TargetGuid)); * actionChain.EnqueueChain();*/ }
/// <summary> /// Wrapper around CreateEnchantment for Creature Magic /// </summary> /// <param name="target"></param> /// <param name="spell"></param> /// <param name="spellStatMod"></param> /// <param name="castByItem"></param> /// <returns></returns> protected string CreatureMagic(WorldObject target, SpellBase spell, Database.Models.World.Spell spellStatMod, string castByItem = null) { return(CreateEnchantment(target, spell, spellStatMod, castByItem)); }
/// <summary> /// Creates the Life Magic spell /// </summary> /// <param name="target"></param> /// <param name="spell"></param> /// <param name="spellStatMod"></param> /// <param name="message"></param> /// <param name="castByItem"></param> /// <returns></returns> protected bool LifeMagic(WorldObject target, SpellBase spell, Database.Models.World.Spell spellStatMod, out string message, string castByItem = null) { string srcVital, destVital, action; string targetMsg = null; Player player = null; Creature creature = null; if (WeenieClassId == 1) { player = (Player)this; } else if (WeenieType == WeenieType.Creature) { creature = (Creature)this; } Creature spellTarget; if (spell.BaseRangeConstant == 0) { spellTarget = (Creature)this; } else { spellTarget = (Creature)target; } int newSpellTargetVital; switch (spell.MetaSpellType) { case SpellType.Boost: int minBoostValue, maxBoostValue; if ((spellStatMod.BoostVariance + spellStatMod.Boost) < spellStatMod.Boost) { minBoostValue = (int)(spellStatMod.BoostVariance + spellStatMod.Boost); maxBoostValue = (int)spellStatMod.Boost; } else { minBoostValue = (int)spellStatMod.Boost; maxBoostValue = (int)(spellStatMod.BoostVariance + spellStatMod.Boost); } int boost = Physics.Common.Random.RollDice(minBoostValue, maxBoostValue); if (boost < 0) { action = "drain"; } else { action = "restore"; } switch (spellStatMod.DamageType) { case 512: // Mana newSpellTargetVital = (int)(spellTarget.Mana.Current + boost); srcVital = "mana"; if (newSpellTargetVital < spellTarget.Mana.MaxValue) { if (newSpellTargetVital <= 0) { spellTarget.UpdateVital(spellTarget.Mana, 0); } else { spellTarget.UpdateVital(spellTarget.Mana, (uint)newSpellTargetVital); } } else { spellTarget.UpdateVital(spellTarget.Mana, spellTarget.Mana.MaxValue); } break; case 256: // Stamina newSpellTargetVital = (int)(spellTarget.Stamina.Current + boost); srcVital = "stamina"; if (newSpellTargetVital < spellTarget.Stamina.MaxValue) { if (newSpellTargetVital <= 0) { spellTarget.UpdateVital(spellTarget.Stamina, 0); } else { spellTarget.UpdateVital(spellTarget.Stamina, (uint)newSpellTargetVital); } } else { spellTarget.UpdateVital(spellTarget.Stamina, spellTarget.Stamina.MaxValue); } break; default: // Health newSpellTargetVital = (int)(spellTarget.Health.Current + boost); srcVital = "health"; if (newSpellTargetVital < spellTarget.Health.MaxValue) { if (newSpellTargetVital <= 0) { spellTarget.UpdateVital(spellTarget.Health, 0); } else { spellTarget.UpdateVital(spellTarget.Health, (uint)newSpellTargetVital); } } else { spellTarget.UpdateVital(spellTarget.Health, spellTarget.Health.MaxValue); } break; } if (this is Player) { if (spell.BaseRangeConstant == 0) { message = $"You {action} {Math.Abs(boost).ToString()} {srcVital}"; } else { message = $"You {action} {Math.Abs(boost).ToString()} points of {srcVital} from {spellTarget.Name}"; } } else { message = null; } if (target is Player && spell.BaseRangeConstant > 0) { targetMsg = $"{Name} casts {spell.Name} and {action}s {Math.Abs(boost)} points of your {srcVital}."; } break; case SpellType.Transfer: // Calculate the change in vitals of the target Creature caster; if (spell.BaseRangeConstant == 0 && spell.BaseRangeMod == 1) { caster = spellTarget; } else { caster = (Creature)this; } uint vitalChange, casterVitalChange; ResistanceType resistanceDrain, resistanceBoost; if (spellStatMod.Source == (int)PropertyAttribute2nd.Mana) { resistanceDrain = ResistanceType.ManaDrain; } else if (spellStatMod.Source == (int)PropertyAttribute2nd.Stamina) { resistanceDrain = ResistanceType.StaminaDrain; } else { resistanceDrain = ResistanceType.HealthDrain; } vitalChange = (uint)((spellTarget.GetCurrentCreatureVital((PropertyAttribute2nd)spellStatMod.Source) * spellStatMod.Proportion) * spellTarget.GetNaturalResistence(resistanceDrain)); if (spellStatMod.TransferCap != 0) { if (vitalChange > spellStatMod.TransferCap) { vitalChange = (uint)spellStatMod.TransferCap; } } if (spellStatMod.Destination == (int)PropertyAttribute2nd.Mana) { resistanceBoost = ResistanceType.ManaDrain; } else if (spellStatMod.Source == (int)PropertyAttribute2nd.Stamina) { resistanceBoost = ResistanceType.StaminaDrain; } else { resistanceBoost = ResistanceType.HealthDrain; } casterVitalChange = (uint)((vitalChange * (1.0f - spellStatMod.LossPercent)) * spellTarget.GetNaturalResistence(resistanceBoost)); vitalChange = (uint)(casterVitalChange / (1.0f - spellStatMod.LossPercent)); newSpellTargetVital = (int)(spellTarget.GetCurrentCreatureVital((PropertyAttribute2nd)spellStatMod.Source) - vitalChange); // Apply the change in vitals to the target switch (spellStatMod.Source) { case (int)PropertyAttribute2nd.Mana: srcVital = "mana"; if (newSpellTargetVital <= 0) { spellTarget.UpdateVital(spellTarget.Mana, 0); } else { spellTarget.UpdateVital(spellTarget.Mana, (uint)newSpellTargetVital); } break; case (int)PropertyAttribute2nd.Stamina: srcVital = "stamina"; if (newSpellTargetVital <= 0) { spellTarget.UpdateVital(spellTarget.Stamina, 0); } else { spellTarget.UpdateVital(spellTarget.Stamina, (uint)newSpellTargetVital); } break; default: // Health srcVital = "health"; if (newSpellTargetVital <= 0) { spellTarget.UpdateVital(spellTarget.Health, 0); } else { spellTarget.UpdateVital(spellTarget.Health, (uint)newSpellTargetVital); } break; } // Apply the scaled change in vitals to the caster uint newCasterVital; switch (spellStatMod.Destination) { case (int)PropertyAttribute2nd.Mana: destVital = "mana"; newCasterVital = caster.Mana.Current + casterVitalChange; caster.UpdateVital(caster.Mana, newCasterVital); break; case (int)PropertyAttribute2nd.Stamina: destVital = "stamina"; newCasterVital = caster.Stamina.Current + casterVitalChange; caster.UpdateVital(caster.Stamina, newCasterVital); break; default: // Health destVital = "health"; newCasterVital = caster.Mana.Current + casterVitalChange; caster.UpdateVital(caster.Health, newCasterVital); break; } if (WeenieClassId == 1) { if (target.Guid == Guid) { message = $"You drain {vitalChange.ToString()} points of {srcVital} and apply {casterVitalChange.ToString()} points of {destVital} to yourself"; } else { message = $"You drain {vitalChange.ToString()} points of {srcVital} from {spellTarget.Name} and apply {casterVitalChange.ToString()} to yourself"; } } else { message = null; } if (target is Player && target != this) { targetMsg = $"You lose {vitalChange} points of {srcVital} due to {Name} casting {spell.Name} on you"; } break; case SpellType.LifeProjectile: caster = (Creature)this; uint damage; if (spell.Name.Contains("Blight")) { damage = (uint)(caster.GetCurrentCreatureVital(PropertyAttribute2nd.Mana) * caster.GetNaturalResistence(ResistanceType.ManaDrain)); newCasterVital = caster.Mana.Current - damage; if (newCasterVital <= 0) { caster.UpdateVital(caster.Mana, 0); } else { caster.UpdateVital(caster.Mana, (uint)newCasterVital); } } else if (spell.Name.Contains("Tenacity")) { damage = (uint)(spellTarget.GetCurrentCreatureVital(PropertyAttribute2nd.Stamina) * spellTarget.GetNaturalResistence(ResistanceType.StaminaDrain)); newCasterVital = caster.Stamina.Current - damage; if (newCasterVital <= 0) { caster.UpdateVital(caster.Stamina, 0); } else { caster.UpdateVital(caster.Stamina, (uint)newCasterVital); } } else { damage = (uint)(spellTarget.GetCurrentCreatureVital(PropertyAttribute2nd.Stamina) * spellTarget.GetNaturalResistence(ResistanceType.HealthDrain)); newCasterVital = caster.Health.Current - damage; if (newCasterVital <= 0) { caster.UpdateVital(caster.Health, 0); } else { caster.UpdateVital(caster.Health, (uint)newCasterVital); } } CreateSpellProjectile(this, target, spell.MetaSpellId, (uint)spellStatMod.Wcid, damage); if (caster.Health.Current <= 0) { caster.Die(); if (caster.WeenieClassId == 1) { Strings.DeathMessages.TryGetValue(DamageType.Base, out var messages); player.Session.Network.EnqueueSend(new GameMessageSystemChat("You have killed yourself", ChatMessageType.Broadcast)); } } message = null; break; case SpellType.Dispel: message = "Spell not implemented, yet!"; break; case SpellType.Enchantment: message = CreateEnchantment(target, spell, spellStatMod, castByItem); break; default: message = "Spell not implemented, yet!"; break; } if (targetMsg != null) { var playerTarget = target as Player; playerTarget.Session.Network.EnqueueSend(new GameMessageSystemChat(targetMsg, ChatMessageType.Magic)); } if (spellTarget.Health.Current == 0) { return(true); } else { return(false); } }
/// <summary> /// Determines whether the target for the spell being cast is invalid /// </summary> /// <param name="spell"></param> /// <param name="target"></param> /// <returns></returns> protected bool IsInvalidTarget(SpellBase spell, WorldObject target) { // Self targeted spells should have a target of self if ((int)Math.Floor(spell.BaseRangeConstant) == 0 && target.WeenieClassId != 1) { return(true); } // Invalidate non Item Enchantment spells cast against non Creatures or Players if (spell.School != MagicSchool.ItemEnchantment) { if (target.WeenieType != WeenieType.Creature) { if (target.WeenieClassId != 1) { return(true); } } } // Invalidate beneficial spells against Creature/Non-player targets if (target.WeenieType == WeenieType.Creature && IsSpellHarmful(spell) == false) { return(true); } // Cannot cast Weapon Aura spells on targets that are not players or creatures if ((spell.Name.Contains("Aura of")) && (spell.School == MagicSchool.ItemEnchantment)) { if (target.WeenieClassId != 1) { if (target.WeenieType != WeenieType.Creature) { return(true); } } } // Cannot cast Weapon Aura spells on targets that are not players or creatures if ((spell.MetaSpellType == SpellType.Enchantment) && (spell.School == MagicSchool.ItemEnchantment)) { if ((target.WeenieClassId == 1) || (target.WeenieType == WeenieType.Creature) || (target.WeenieType == WeenieType.Clothing) || (target.WeenieType == WeenieType.Caster) || (target.WeenieType == WeenieType.MeleeWeapon) || (target.WeenieType == WeenieType.MissileLauncher) || (target.WeenieType == WeenieType.Missile) || (target.WeenieType == WeenieType.Door) || (target.WeenieType == WeenieType.Chest)) { return(false); } else { return(true); } } return(false); }
/// <summary> /// Returns TRUE if the creature receives a +5 DR bonus for this weapon type /// </summary> public virtual bool GetHeritageBonus(WorldObject weapon) { // only for players return(false); }
private List <WorldObject> SpendCurrency(uint amount, WeenieType type) { if (type == WeenieType.Coin && amount > CoinValue) { return(null); } List <WorldObject> currency = new List <WorldObject>(); currency.AddRange(GetInventoryItemsOfTypeWeenieType(type)); currency = currency.OrderBy(o => o.Value).ToList(); List <WorldObject> cost = new List <WorldObject>(); uint payment = 0; WorldObject changeobj = WorldObjectFactory.CreateNewWorldObject(273); uint change = 0; foreach (WorldObject wo in currency) { if (payment + wo.StackSize.Value <= amount) { // add to payment payment = payment + (uint)wo.StackSize.Value; cost.Add(wo); } else if (payment + wo.StackSize.Value > amount) { // add payment payment = payment + (uint)wo.StackSize.Value; cost.Add(wo); // calculate change if (payment > amount) { change = payment - amount; // add new change object. changeobj.SetStackSize((int)change); wo.SetStackSize(wo.StackSize - (int)change); } break; } else if (payment == amount) { break; } } // destroy all stacks of currency required / sale foreach (WorldObject wo in cost) { TryConsumeFromInventoryWithNetworking(wo); } // if there is change - readd - do this at the end to try to prevent exploiting if (change > 0) { TryCreateInInventoryWithNetworking(changeobj); } UpdateCoinValue(false); return(cost); }
/// <summary> /// Translates the default combat style for a weapon /// into a combat motion stance /// </summary> public MotionStance GetWeaponStance(WorldObject weapon) { var combatStance = MotionStance.HandCombat; switch (weapon.DefaultCombatStyle) { case CombatStyle.Atlatl: combatStance = MotionStance.AtlatlCombat; break; case CombatStyle.Bow: combatStance = MotionStance.BowCombat; break; case CombatStyle.Crossbow: combatStance = MotionStance.CrossbowCombat; break; case CombatStyle.DualWield: combatStance = MotionStance.DualWieldCombat; break; case CombatStyle.Magic: combatStance = MotionStance.Magic; break; case CombatStyle.OneHanded: combatStance = MotionStance.SwordCombat; break; case CombatStyle.OneHandedAndShield: combatStance = MotionStance.SwordShieldCombat; break; case CombatStyle.Sling: combatStance = MotionStance.SlingCombat; break; case CombatStyle.ThrownShield: combatStance = MotionStance.ThrownShieldCombat; break; case CombatStyle.ThrownWeapon: combatStance = MotionStance.ThrownWeaponCombat; break; case CombatStyle.TwoHanded: // MotionStance.TwoHandedStaffCombat doesn't appear to do anything // Additionally, PropertyInt.WeaponType isn't always included, and the 2handed weapons that do appear to use WeaponType.TwoHanded combatStance = MotionStance.TwoHandedSwordCombat; break; case CombatStyle.Unarmed: combatStance = MotionStance.HandCombat; break; default: Console.WriteLine($"{Name}.GetCombatStance() - {weapon.DefaultCombatStyle}"); break; } return(combatStance); }
public override void OnCollideObject(WorldObject target) { //Console.WriteLine($"{Name}.OnCollideObject({target.Name})"); var player = ProjectileSource as Player; if (Info != null && player != null && player.DebugSpell) { player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name}.OnCollideObject({target?.Name} ({target?.Guid}))", ChatMessageType.Broadcast)); player.Session.Network.EnqueueSend(new GameMessageSystemChat(Info.ToString(), ChatMessageType.Broadcast)); } ProjectileImpact(); // ensure valid creature target var creatureTarget = target as Creature; if (creatureTarget == null || target == ProjectileSource) { return; } if (player != null) { player.LastHitSpellProjectile = Spell; } // ensure caster can damage target var sourceCreature = ProjectileSource as Creature; if (sourceCreature != null && !sourceCreature.CanDamage(creatureTarget)) { return; } // if player target, ensure matching PK status var targetPlayer = creatureTarget as Player; var pkError = ProjectileSource?.CheckPKStatusVsTarget(creatureTarget, Spell); if (pkError != null) { if (player != null) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, pkError[0], creatureTarget.Name)); } if (targetPlayer != null) { targetPlayer.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(targetPlayer.Session, pkError[1], ProjectileSource.Name)); } return; } var critical = false; var critDefended = false; var overpower = false; var damage = CalculateDamage(ProjectileSource, Caster, creatureTarget, ref critical, ref critDefended, ref overpower); if (damage != null) { // handle void magic DoTs: // instead of instant damage, add DoT to target's enchantment registry if (Spell.School == MagicSchool.VoidMagic && Spell.Duration > 0) { var dot = ProjectileSource.CreateEnchantment(creatureTarget, ProjectileSource, Spell); if (dot.Message != null && player != null) { player.Session.Network.EnqueueSend(dot.Message); } // corruption / corrosion playscript? //target.EnqueueBroadcast(new GameMessageScript(target.Guid, PlayScript.HealthDownVoid)); //target.EnqueueBroadcast(new GameMessageScript(target.Guid, PlayScript.DirtyFightingDefenseDebuff)); } else { DamageTarget(creatureTarget, damage.Value, critical, critDefended, overpower); } if (player != null) { Proficiency.OnSuccessUse(player, player.GetCreatureSkill(Spell.School), Spell.PowerMod); } // handle target procs // note that for untargeted multi-projectile spells, // ProjectileTarget will be null here, so procs will not apply if (sourceCreature != null && ProjectileTarget != null) { // TODO figure out why cross-landblock group operations are happening here. We shouldn't need this code Mag-nus 2021-02-09 bool threadSafe = true; if (LandblockManager.CurrentlyTickingLandblockGroupsMultiThreaded) { // Ok... if we got here, we're likely in the parallel landblock physics processing. if (sourceCreature.CurrentLandblock == null || creatureTarget.CurrentLandblock == null || sourceCreature.CurrentLandblock.CurrentLandblockGroup != creatureTarget.CurrentLandblock.CurrentLandblockGroup) { threadSafe = false; } } if (threadSafe) { // This can result in spell projectiles being added to either sourceCreature or creatureTargets landblock. sourceCreature.TryProcEquippedItems(creatureTarget, false); } else { // sourceCreature and creatureTarget are now in different landblock groups. // What has likely happened is that sourceCreature sent a projectile toward creatureTarget. Before impact, sourceCreature was teleported away. // To perform this fully thread safe, we would enqueue the work onto worldManager. // WorldManager.EnqueueAction(new ActionEventDelegate(() => sourceCreature.TryProcEquippedItems(creatureTarget, false))); // But, to keep it simple, we will just ignore it and not bother with TryProcEquippedItems for this particular impact. } } } // also called on resist if (player != null && targetPlayer == null) { player.OnAttackMonster(creatureTarget); } if (player == null && targetPlayer == null) { // check for faction combat if (sourceCreature != null && creatureTarget != null && (sourceCreature.AllowFactionCombat(creatureTarget) || sourceCreature.PotentialFoe(creatureTarget))) { sourceCreature.MonsterOnAttackMonster(creatureTarget); } } }
/// <summary> /// Called when a creature evades an attack /// </summary> public virtual void OnEvade(WorldObject attacker, CombatType attackType) { // empty base for non-player creatures? }
/// <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; //http://acpedia.org/wiki/Announcements_-_2014/01_-_Forces_of_Nature - Aegis is 72% effective in PvP if (isPVP && (target.CombatMode == CombatMode.Melee || target.CombatMode == CombatMode.Missile)) { absorbMod = 1 - absorbMod; absorbMod *= 0.72f; absorbMod = 1 - absorbMod; } 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); }
/// <summary> /// Returns a divisor for the target height /// for aiming projectiles /// </summary> public virtual float GetAimHeight(WorldObject target) { return(2.0f); }
/// <summary> /// Called for a spell projectile to damage its target /// </summary> public void DamageTarget(Creature target, float damage, bool critical, bool critDefended, bool overpower) { var targetPlayer = target as Player; if (targetPlayer != null && targetPlayer.Invincible || target.IsDead) { return; } var sourceCreature = ProjectileSource as Creature; var sourcePlayer = ProjectileSource as Player; var amount = 0u; var percent = 0.0f; var heritageMod = 1.0f; var sneakAttackMod = 1.0f; var damageRatingMod = 1.0f; var damageResistRatingMod = 1.0f; WorldObject equippedCloak = null; // handle life projectiles for stamina / mana if (Spell.Category == SpellCategory.StaminaLowering) { percent = damage / target.Stamina.MaxValue; amount = (uint)-target.UpdateVitalDelta(target.Stamina, (int)-Math.Round(damage)); } else if (Spell.Category == SpellCategory.ManaLowering) { percent = damage / target.Mana.MaxValue; amount = (uint)-target.UpdateVitalDelta(target.Mana, (int)-Math.Round(damage)); } else { // for possibly applying sneak attack to magic projectiles, // only do this for health-damaging projectiles? if (sourcePlayer != null) { // TODO: use target direction vs. projectile position, instead of player position // could sneak attack be applied to void DoTs? sneakAttackMod = sourcePlayer.GetSneakAttackMod(target); //Console.WriteLine("Magic sneak attack: + sneakAttackMod); heritageMod = sourcePlayer.GetHeritageBonus(sourcePlayer.GetEquippedWand()) ? 1.05f : 1.0f; } // DR / DRR applies for magic too? var damageRating = sourceCreature?.GetDamageRating() ?? 0; damageRatingMod = Creature.AdditiveCombine(Creature.GetPositiveRatingMod(damageRating), heritageMod, sneakAttackMod); damageResistRatingMod = target.GetDamageResistRatingMod(CombatType.Magic); damage *= damageRatingMod * damageResistRatingMod; percent = damage / target.Health.MaxValue; //Console.WriteLine($"Damage rating: " + Creature.ModToRating(damageRatingMod)); equippedCloak = target.EquippedCloak; if (equippedCloak != null && Cloak.HasDamageProc(equippedCloak) && Cloak.RollProc(equippedCloak, percent)) { var reducedDamage = Cloak.GetReducedAmount(ProjectileSource, damage); Cloak.ShowMessage(target, ProjectileSource, damage, reducedDamage); damage = reducedDamage; percent = damage / target.Health.MaxValue; } amount = (uint)-target.UpdateVitalDelta(target.Health, (int)-Math.Round(damage)); target.DamageHistory.Add(ProjectileSource, Spell.DamageType, amount); //if (targetPlayer != null && targetPlayer.Fellowship != null) //targetPlayer.Fellowship.OnVitalUpdate(targetPlayer); } amount = (uint)Math.Round(damage); // full amount for debugging if (target.IsAlive) { string verb = null, plural = null; Strings.GetAttackVerb(Spell.DamageType, percent, ref verb, ref plural); var type = Spell.DamageType.GetName().ToLower(); var critMsg = critical ? "Critical hit! " : ""; var sneakMsg = sneakAttackMod > 1.0f ? "Sneak Attack! " : ""; var overpowerMsg = overpower ? "Overpower! " : ""; var nonHealth = Spell.Category == SpellCategory.StaminaLowering || Spell.Category == SpellCategory.ManaLowering; if (sourcePlayer != null) { var critProt = critDefended ? " Your critical hit was avoided with their augmentation!" : ""; var attackerMsg = $"{critMsg}{overpowerMsg}{sneakMsg}You {verb} {target.Name} for {amount} points with {Spell.Name}.{critProt}"; // could these crit / sneak attack? if (nonHealth) { var vital = Spell.Category == SpellCategory.StaminaLowering ? "stamina" : "mana"; attackerMsg = $"With {Spell.Name} you drain {amount} points of {vital} from {target.Name}."; } if (!sourcePlayer.SquelchManager.Squelches.Contains(target, ChatMessageType.Magic)) { sourcePlayer.Session.Network.EnqueueSend(new GameMessageSystemChat(attackerMsg, ChatMessageType.Magic)); } } if (targetPlayer != null) { var critProt = critDefended ? " Your augmentation allows you to avoid a critical hit!" : ""; var defenderMsg = $"{critMsg}{overpowerMsg}{sneakMsg}{ProjectileSource.Name} {plural} you for {amount} points with {Spell.Name}.{critProt}"; if (nonHealth) { var vital = Spell.Category == SpellCategory.StaminaLowering ? "stamina" : "mana"; defenderMsg = $"{ProjectileSource.Name} casts {Spell.Name} and drains {amount} points of your {vital}."; } if (!targetPlayer.SquelchManager.Squelches.Contains(ProjectileSource, ChatMessageType.Magic)) { targetPlayer.Session.Network.EnqueueSend(new GameMessageSystemChat(defenderMsg, ChatMessageType.Magic)); } if (sourceCreature != null) { targetPlayer.SetCurrentAttacker(sourceCreature); } } if (!nonHealth) { if (equippedCloak != null && Cloak.HasProcSpell(equippedCloak)) { Cloak.TryProcSpell(target, ProjectileSource, equippedCloak, percent); } target.EmoteManager.OnDamage(sourcePlayer); if (critical) { target.EmoteManager.OnReceiveCritical(sourcePlayer); } } } else { var lastDamager = ProjectileSource != null ? new DamageHistoryInfo(ProjectileSource) : null; target.OnDeath(lastDamager, Spell.DamageType, critical); target.Die(); } // show debug info if (sourceCreature != null && sourceCreature.DebugDamage.HasFlag(Creature.DebugDamageType.Attacker)) { ShowInfo(sourceCreature, heritageMod, sneakAttackMod, damageRatingMod, damageResistRatingMod, damage); } if (target.DebugDamage.HasFlag(Creature.DebugDamageType.Defender)) { ShowInfo(target, heritageMod, sneakAttackMod, damageRatingMod, damageResistRatingMod, damage); } }
public float GetSneakAttackMod(WorldObject target) { // ensure trained var sneakAttack = GetCreatureSkill(Skill.SneakAttack); if (sneakAttack.AdvancementClass < SkillAdvancementClass.Trained) { return(1.0f); } // ensure creature target var creatureTarget = target as Creature; if (creatureTarget == null) { return(1.0f); } // Effects: // General Sneak Attack effects: // - 100% chance to sneak attack from behind an opponent. // - Deception trained: 10% chance to sneak attack from the front of an opponent // - Deception specialized: 15% chance to sneak attack from the front of an opponent var angle = creatureTarget.GetAngle(this); var behind = Math.Abs(angle) > 90.0f; var chance = 0.0f; if (behind) { chance = 1.0f; } else { var deception = GetCreatureSkill(Skill.Deception); if (deception.AdvancementClass == SkillAdvancementClass.Trained) { chance = 0.1f; } else if (deception.AdvancementClass == SkillAdvancementClass.Specialized) { chance = 0.15f; } // if Deception is below 306 skill, these chances are reduced proportionately. // this is in addition to proprtional reduction if your Sneak Attack skill is below your attack skill. var deceptionCap = 306; if (deception.Current < deceptionCap) { chance *= Math.Min((float)deception.Current / deceptionCap, 1.0f); } } //Console.WriteLine($"Sneak attack {(behind ? "behind" : "front")}, chance {Math.Round(chance * 100)}%"); var rng = ThreadSafeRandom.Next(0.0f, 1.0f); if (rng > chance) { return(1.0f); } // Damage Rating: // Sneak Attack Trained: // + 10 Damage Rating when Sneak Attack activates // Sneak Attack Specialized: // + 20 Damage Rating when Sneak Attack activates var damageRating = sneakAttack.AdvancementClass == SkillAdvancementClass.Specialized ? 20.0f : 10.0f; // Sneak Attack works for melee, missile, and magic attacks. // if the Sneak Attack skill is lower than your attack skill (as determined by your equipped weapon) // then the damage rating is reduced proportionately. Because the damage rating caps at 10 for trained // and 20 for specialized, there is no reason to raise the skill above your attack skill var attackSkill = GetCreatureSkill(GetCurrentAttackSkill()); if (sneakAttack.Current < attackSkill.Current) { if (attackSkill.Current > 0) { damageRating *= (float)sneakAttack.Current / attackSkill.Current; } else { damageRating = 0; } } // if the defender has Assess Person, they reduce the extra Sneak Attack damage Deception can add // from the front by up to 100%. // this percent is reduced proportionately if your buffed Assess Person skill is below the deception cap. // this reduction does not apply to attacks from behind. if (!behind) { // compare to assess person or deception?? // wiki info is confusing here, it says 'your buffed Assess Person' // which sounds like its scaling sourceAssess / targetAssess, // but i think it should be targetAssess / deceptionCap? var targetAssess = creatureTarget.GetCreatureSkill(Skill.AssessPerson).Current; var deceptionCap = 306; damageRating *= 1.0f - Math.Min((float)targetAssess / deceptionCap, 1.0f); } var sneakAttackMod = (100 + damageRating) / 100.0f; //Console.WriteLine("SneakAttackMod: " + sneakAttackMod); return(sneakAttackMod); }
public static void UseUnlocker(Player player, WorldObject unlocker, WorldObject target) { ActionChain chain = new ActionChain(); chain.AddAction(player, () => { if (unlocker.WeenieType == WeenieType.Lockpick && player.Skills[Skill.Lockpick].AdvancementClass != SkillAdvancementClass.Trained && player.Skills[Skill.Lockpick].AdvancementClass != SkillAdvancementClass.Specialized) { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouArentTrainedInLockpicking)); return; } if (target is Lock @lock) { UnlockResults result = UnlockResults.IncorrectKey; var difficulty = 0; if (unlocker.WeenieType == WeenieType.Lockpick) { result = @lock.Unlock(player.Guid.Full, player.Skills[Skill.Lockpick].Current, ref difficulty); } else if (unlocker is Key woKey) { if (target is Door woDoor) { if (woDoor.LockCode == "") // the door isn't to be opened with keys { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); return; } } result = @lock.Unlock(player.Guid.Full, woKey.KeyCode); } switch (result) { case UnlockResults.UnlockSuccess: if (unlocker.WeenieType == WeenieType.Lockpick) { player.HandleActionApplySoundEffect(Sound.Lockpicking); // Sound.Lockpicking doesn't work via EnqueueBroadcastSound for some reason. player.Session.Network.EnqueueSend(new GameMessageSystemChat($"You have successfully picked the lock! It is now unlocked.", ChatMessageType.Broadcast)); var lockpickSkill = player.GetCreatureSkill(Skill.Lockpick); Proficiency.OnSuccessUse(player, lockpickSkill, difficulty); } else { player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{target.Name} has been unlocked.", ChatMessageType.Broadcast)); } ConsumeUnlocker(player, unlocker); break; case UnlockResults.Open: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockWhatIsOpen)); break; case UnlockResults.AlreadyUnlocked: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.LockAlreadyUnlocked)); break; case UnlockResults.PickLockFailed: target.EnqueueBroadcast(new GameMessageSound(target.Guid, Sound.PicklockFail, 1.0f)); ConsumeUnlocker(player, unlocker); break; case UnlockResults.CannotBePicked: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); break; case UnlockResults.IncorrectKey: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.KeyDoesntFitThisLock)); break; } } else { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); } }); chain.EnqueueChain(); }
public override void SetLinkProperties(WorldObject wo) { wo.ActivationTarget = Guid.Full; }
/// <summary> /// Compares the number of body parts covered by 2 pieces of clothing /// </summary> public int ValidLocationComparer(WorldObject a, WorldObject b) { return(EnumHelper.NumFlags((uint)a.ValidLocations).CompareTo(EnumHelper.NumFlags((uint)b.ValidLocations))); }
public override void ActOnUse(WorldObject wo) { // Do nothing }
/// <summary> /// Compares the number of body parts covered by 2 pieces of clothing /// </summary> public int ArmorLevelComparer(WorldObject a, WorldObject b) { return(((uint)a.ArmorLevel).CompareTo((uint)b.ArmorLevel)); }
public WorldObject(ACE.Server.WorldObjects.WorldObject wo) { _wo = wo; }
public void HandleActionUseOnTarget(Player player, WorldObject target) { ActionChain chain = new ActionChain(); chain.AddAction(player, () => { if (target.WeenieType == WeenieType.Door) { var door = (Door)target; if (player.Skills[Skill.Lockpick].Status != SkillStatus.Trained && player.Skills[Skill.Lockpick].Status != SkillStatus.Specialized) { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouArentTrainedInLockpicking)); return; } void consume(Player plr, Lockpick lp) { lp.Structure--; if (lp.Structure < 1) { plr.TryRemoveItemFromInventoryWithNetworking(this, 1); } plr.Session.Network.EnqueueSend(new GameEventUseDone(plr.Session)); plr.Session.Network.EnqueueSend(new GameMessagePublicUpdatePropertyInt(lp, PropertyInt.Structure, (int)lp.Structure)); plr.Session.Network.EnqueueSend(new GameMessageSystemChat($"Uses reamaining: {Structure}", ChatMessageType.System)); } Door.UnlockDoorResults results = door.UnlockDoor(player.Skills[Skill.Lockpick].Current); switch (results) { case Door.UnlockDoorResults.UnlockSuccess: consume(player, this); break; case Door.UnlockDoorResults.DoorOpen: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockWhatIsOpen)); break; case Door.UnlockDoorResults.AlreadyUnlocked: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.LockAlreadyUnlocked)); break; case Door.UnlockDoorResults.PickLockFailed: consume(player, this); break; default: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.KeyDoesntFitThisLock)); break; } } else if (target.WeenieType == WeenieType.Chest) { var message = new GameMessageSystemChat($"Unlocking {target.Name} has not been implemented, yet!", ChatMessageType.System); player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session), message); } else { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); } }); chain.EnqueueChain(); }