Example #1
0
    private (double Heal, DamageOutput Damage) CalculateNumericEffects(Xml.Castable castable, Xml.ModifierEffect effect, Creature source)
    {
        double heal = 0;
        var    dmg  = new DamageOutput();

        if (effect == null)
        {
            return(heal, dmg);
        }
        if (effect.Heal != null)
        {
            heal = NumberCruncher.CalculateHeal(castable, effect, Target, source, Name);
        }
        if (effect.Damage != null)
        {
            dmg = NumberCruncher.CalculateDamage(castable, effect, Target, source, Name);
        }
        return(heal, dmg);
    }
Example #2
0
        public virtual bool UseCastable(Xml.Castable castObject, Creature target = null, Xml.SpawnCastable spawnCastable = null)
        {
            if (!Condition.CastingAllowed)
            {
                return(false);
            }

            if (this is User)
            {
                GameLog.UserActivityInfo($"UseCastable: {Name} begin casting {castObject.Name} on target: {target?.Name ?? "no target"} CastingAllowed: {Condition.CastingAllowed}");
            }

            var             damage  = castObject.Effects.Damage;
            List <Creature> targets = new List <Creature>();

            if (this is Monster)
            {
                if (spawnCastable != null)
                {
                    damage = new Xml.CastableDamage
                    {
                        Simple = new Xml.SimpleQuantity
                        {
                            Min = (uint)spawnCastable.MinDmg,
                            Max = (uint)spawnCastable.MaxDmg
                        }
                    };

                    castObject.Effects.Damage = damage;                //set damage based on spawncastable settings.
                    castObject.Element        = spawnCastable.Element; //handle defined element without redoing a ton of code.
                }
            }

            targets = GetTargets(castObject, target);

            // Quick checks
            // If no targets and is not an assail, do nothing
            if (targets.Count() == 0 && castObject.IsAssail == false && string.IsNullOrEmpty(castObject.Script))
            {
                GameLog.UserActivityInfo($"UseCastable: {Name}: no targets and not assail");
                return(false);
            }
            // Is this a pvpable spell? If so, is pvp enabled?

            // We do these next steps to ensure effects are displayed uniformly and as fast as possible
            var deadMobs = new List <Creature>();

            if (castObject.Effects?.Animations?.OnCast != null)
            {
                foreach (var tar in targets)
                {
                    foreach (var user in tar.viewportUsers.ToList())
                    {
                        GameLog.UserActivityInfo($"UseCastable: Sending {user.Name} effect for {Name}: {castObject.Effects.Animations.OnCast.Target.Id}");
                        user.SendEffect(tar.Id, castObject.Effects.Animations.OnCast.Target.Id, castObject.Effects.Animations.OnCast.Target.Speed);
                    }
                }
                if (castObject.Effects?.Animations?.OnCast?.SpellEffect != null)
                {
                    GameLog.UserActivityInfo($"UseCastable: Sending spelleffect for {Name}: {castObject.Effects.Animations.OnCast.SpellEffect.Id}");
                    Effect(castObject.Effects.Animations.OnCast.SpellEffect.Id, castObject.Effects.Animations.OnCast.SpellEffect.Speed);
                }
            }

            if (castObject.Effects?.Sound != null)
            {
                PlaySound(castObject.Effects.Sound.Id);
            }

            GameLog.UserActivityInfo($"UseCastable: {Name} casting {castObject.Name}, {targets.Count()} targets");

            if (!string.IsNullOrEmpty(castObject.Script))
            {
                // If a script is defined we fire it immediately, and let it handle targeting / etc
                if (Game.World.ScriptProcessor.TryGetScript(castObject.Script, out Script script))
                {
                    return(script.ExecuteFunction("OnUse", this));
                }
                else
                {
                    GameLog.UserActivityError($"UseCastable: {Name} casting {castObject.Name}: castable script {castObject.Script} missing");
                    return(false);
                }
            }
            if (targets.Count == 0)
            {
                GameLog.UserActivityError("{Name}: {castObject.Name}: hey fam no targets");
            }

            foreach (var tar in targets)
            {
                if (castObject.Effects?.ScriptOverride == true)
                {
                    // TODO: handle castables with scripting
                    // DoStuff();
                    continue;
                }
                if (!castObject.Effects.Damage.IsEmpty)
                {
                    Xml.Element attackElement;
                    var         damageOutput = NumberCruncher.CalculateDamage(castObject, tar, this);
                    if (castObject.Element == Xml.Element.Random)
                    {
                        Random rnd      = new Random();
                        var    Elements = Enum.GetValues(typeof(Xml.Element));
                        attackElement = (Xml.Element)Elements.GetValue(rnd.Next(Elements.Length));
                    }
                    else if (castObject.Element != Xml.Element.None)
                    {
                        attackElement = castObject.Element;
                    }
                    else
                    {
                        attackElement = (Stats.OffensiveElementOverride != Xml.Element.None ? Stats.OffensiveElementOverride : Stats.BaseOffensiveElement);
                    }
                    GameLog.UserActivityInfo($"UseCastable: {Name} casting {castObject.Name} - target: {tar.Name} damage: {damageOutput}, element {attackElement}");

                    tar.Damage(damageOutput.Amount, attackElement, damageOutput.Type, damageOutput.Flags, this, false);

                    if (this is Monster)
                    {
                        if (tar is User)
                        {
                            (tar as User).SendSystemMessage($"{this.Name} attacks you with {castObject.Name}.");
                        }
                    }

                    if (this is User)
                    {
                        if (Equipment.Weapon != null && !Equipment.Weapon.Undamageable)
                        {
                            Equipment.Weapon.Durability -= 1 / (Equipment.Weapon.MaximumDurability * ((100 - Stats.Ac) == 0 ? 1 : (100 - Stats.Ac)));
                        }
                    }

                    if (tar.Stats.Hp <= 0)
                    {
                        deadMobs.Add(tar);
                    }
                }
                // Note that we ignore castables with both damage and healing effects present - one or the other.
                // A future improvement might be to allow more complex effects.
                else if (!castObject.Effects.Heal.IsEmpty)
                {
                    var healOutput = NumberCruncher.CalculateHeal(castObject, tar, this);
                    tar.Heal(healOutput, this);
                    if (this is User)
                    {
                        GameLog.UserActivityInfo($"UseCastable: {Name} casting {castObject.Name} - target: {tar.Name} healing: {healOutput}");
                        if (Equipment.Weapon != null && !Equipment.Weapon.Undamageable)
                        {
                            Equipment.Weapon.Durability -= 1 / (Equipment.Weapon.MaximumDurability * ((100 - Stats.Ac) == 0 ? 1 : (100 - Stats.Ac)));
                        }
                    }
                }

                // Handle statuses

                foreach (var status in castObject.Effects.Statuses.Add.Where(e => e.Value != null))
                {
                    Xml.Status applyStatus;
                    if (World.WorldData.TryGetValue <Xml.Status>(status.Value.ToLower(), out applyStatus))
                    {
                        var duration = status.Duration == 0 ? applyStatus.Duration : status.Duration;
                        GameLog.UserActivityInfo($"UseCastable: {Name} casting {castObject.Name} - applying status {status.Value} - duration {duration}");
                        tar.ApplyStatus(new CreatureStatus(applyStatus, tar, castObject, this, duration));
                    }
                    else
                    {
                        GameLog.UserActivityError($"UseCastable: {Name} casting {castObject.Name} - failed to add status {status.Value}, does not exist!");
                    }
                }

                foreach (var status in castObject.Effects.Statuses.Remove)
                {
                    Xml.Status applyStatus;
                    if (World.WorldData.TryGetValue <Xml.Status>(status.ToLower(), out applyStatus))
                    {
                        GameLog.UserActivityError($"UseCastable: {Name} casting {castObject.Name} - removing status {status}");
                        tar.RemoveStatus(applyStatus.Icon);
                    }
                    else
                    {
                        GameLog.UserActivityError($"UseCastable: {Name} casting {castObject.Name} - failed to remove status {status}, does not exist!");
                    }
                }
            }

            // Now flood away
            foreach (var dead in deadMobs)
            {
                World.ControlMessageQueue.Add(new HybrasylControlMessage(ControlOpcodes.HandleDeath, dead));
            }
            Condition.Casting = false;
            return(true);
        }
Example #3
0
        public virtual bool UseCastable(Castable castObject, Creature target = null)
        {
            if (!Condition.CastingAllowed)
            {
                return(false);
            }

            if (this is User)
            {
                ActivityLogger.Info($"UseCastable: {Name} begin casting {castObject.Name} on target: {target?.Name ?? "no target"} CastingAllowed: {Condition.CastingAllowed}");
            }

            var             damage = castObject.Effects.Damage;
            List <Creature> targets;

            targets = GetTargets(castObject, target);

            if (targets.Count() == 0)
            {
                return(false);
            }

            // We do these next steps to ensure effects are displayed uniformly and as fast as possible
            var deadMobs = new List <Creature>();

            foreach (var tar in targets)
            {
                foreach (var user in tar.viewportUsers)
                {
                    user.SendEffect(tar.Id, castObject.Effects.Animations.OnCast.Target.Id, castObject.Effects.Animations.OnCast.Target.Speed);
                }
            }

            if (castObject.Effects?.Animations?.OnCast?.SpellEffect != null)
            {
                Effect(castObject.Effects.Animations.OnCast.SpellEffect.Id, castObject.Effects.Animations.OnCast.SpellEffect.Speed);
            }

            if (castObject.Effects.Sound != null)
            {
                PlaySound(castObject.Effects.Sound.Id);
            }

            ActivityLogger.Info($"UseCastable: {Name} casting {castObject.Name}, {targets.Count()} targets");
            foreach (var tar in targets)
            {
                if (castObject.Effects?.ScriptOverride == true)
                {
                    // TODO: handle castables with scripting
                    // DoStuff();
                    continue;
                }
                if (castObject.Effects?.Damage != null)
                {
                    Enums.Element attackElement;
                    var           damageOutput = NumberCruncher.CalculateDamage(castObject, tar, this);
                    if (castObject.Element == Castables.Element.Random)
                    {
                        Random rnd      = new Random();
                        var    Elements = Enum.GetValues(typeof(Enums.Element));
                        attackElement = (Enums.Element)Elements.GetValue(rnd.Next(Elements.Length));
                    }
                    else if (castObject.Element != Castables.Element.None)
                    {
                        attackElement = (Enums.Element)castObject.Element;
                    }
                    else
                    {
                        attackElement = (Stats.OffensiveElementOverride == Enums.Element.None ? Stats.OffensiveElementOverride : Stats.OffensiveElement);
                    }
                    if (this is User)
                    {
                        ActivityLogger.Info($"UseCastable: {Name} casting {castObject.Name} - target: {tar.Name} damage: {damageOutput}, element {attackElement}");
                    }

                    tar.Damage(damageOutput.Amount, attackElement, damageOutput.Type, damageOutput.Flags, this, false);
                    if (tar.Stats.Hp <= 0)
                    {
                        deadMobs.Add(tar);
                    }
                }
                // Note that we ignore castables with both damage and healing effects present - one or the other.
                // A future improvement might be to allow more complex effects.
                else if (castObject.Effects.Heal != null)
                {
                    var healOutput = NumberCruncher.CalculateHeal(castObject, tar, this);
                    tar.Heal(healOutput, this);
                    if (this is User)
                    {
                        ActivityLogger.Info($"UseCastable: {Name} casting {castObject.Name} - target: {tar.Name} healing: {healOutput}");
                    }
                }

                // Handle statuses

                foreach (var status in castObject.Effects.Statuses.Add.Where(e => e.Value != null))
                {
                    Status applyStatus;
                    if (World.WorldData.TryGetValueByIndex <Status>(status.Value, out applyStatus))
                    {
                        ActivityLogger.Info($"UseCastable: {Name} casting {castObject.Name} - applying status {status.Value}");
                        ApplyStatus(new CreatureStatus(applyStatus, tar, castObject));
                    }
                    else
                    {
                        ActivityLogger.Error($"UseCastable: {Name} casting {castObject.Name} - failed to add status {status.Value}, does not exist!");
                    }
                }

                foreach (var status in castObject.Effects.Statuses.Remove)
                {
                    Status applyStatus;
                    if (World.WorldData.TryGetValueByIndex <Status>(status, out applyStatus))
                    {
                        ActivityLogger.Error($"UseCastable: {Name} casting {castObject.Name} - removing status {status}");
                        RemoveStatus(applyStatus.Icon);
                    }
                    else
                    {
                        ActivityLogger.Error($"UseCastable: {Name} casting {castObject.Name} - failed to remove status {status}, does not exist!");
                    }
                }
            }
            // Now flood away
            foreach (var dead in deadMobs)
            {
                World.ControlMessageQueue.Add(new HybrasylControlMessage(ControlOpcodes.HandleDeath, dead));
            }

            return(true);
        }