private static bool AssignMagic_Gem_New(WorldObject wo, TreasureDeath profile, TreasureRoll roll)
        {
            // TODO: move to standard AssignMagic() pipeline

            var spell = SpellSelectionTable.Roll(1);

            var spellLevel = SpellLevelChance.Roll(profile.Tier);

            var spellLevels = SpellLevelProgression.GetSpellLevels(spell);

            if (spellLevels == null || spellLevels.Count != 8)
            {
                log.Error($"AssignMagic_Gem_New({wo.Name}, {profile.TreasureType}, {roll.ItemType}) - unknown spell {spell}");
                return(false);
            }

            var finalSpellId = spellLevels[spellLevel - 1];

            wo.SpellDID = (uint)finalSpellId;

            var _spell = new Server.Entity.Spell(finalSpellId);

            // retail spellcraft was capped at 370
            wo.ItemSpellcraft = Math.Min((int)_spell.Power, 370);

            var castableMana = (int)_spell.BaseMana * 5;

            wo.ItemMaxMana = RollItemMaxMana_New(wo, roll, castableMana);
            wo.ItemCurMana = wo.ItemMaxMana;

            // verified
            wo.ItemManaCost = castableMana;

            return(true);
        }
        private static float RollEnchantmentDifficulty(List <SpellId> spellIds)
        {
            var spells = new List <Server.Entity.Spell>();

            foreach (var spellId in spellIds)
            {
                var spell = new Server.Entity.Spell(spellId);
                spells.Add(spell);
            }

            spells = spells.OrderBy(i => i.Formula.Level).ToList();

            var itemDifficulty = 0.0f;

            // exclude highest spell
            for (var i = 0; i < spells.Count - 1; i++)
            {
                var spell = spells[i];

                var rng = (float)ThreadSafeRandom.Next(0.5f, 1.5f);

                itemDifficulty += spell.Formula.Level * 5.0f * rng;
            }

            return(itemDifficulty);
        }
Beispiel #3
0
        /// <summary>
        /// Applies the boost from the consumable, broadcasts the sound,
        /// sends message to player, and consumes from inventory
        /// </summary>
        public void ApplyConsumable(Player player)
        {
            var buffType = (ConsumableBuffType)BoosterEnum;
            GameMessageSystemChat buffMessage = null;

            // spells
            if (buffType == ConsumableBuffType.Spell)
            {
                var spellID = SpellDID ?? 0;

                var result = player.CreateSingleSpell(spellID);

                if (result)
                {
                    var spell = new Server.Entity.Spell(spellID);
                    buffMessage = new GameMessageSystemChat($"{Name} casts {spell.Name} on you.", ChatMessageType.Magic);
                }
                else
                {
                    buffMessage = new GameMessageSystemChat($"{Name} has invalid spell id {spellID}", ChatMessageType.Broadcast);
                }
            }
            // vitals
            else
            {
                var vital = player.GetCreatureVital(BoosterEnum);

                if (vital != null)
                {
                    var vitalChange = (uint)Math.Abs(player.UpdateVitalDelta(vital, BoostValue));

                    if (BoosterEnum == PropertyAttribute2nd.Health)
                    {
                        if (BoostValue >= 0)
                        {
                            player.DamageHistory.OnHeal(vitalChange);
                        }
                        else
                        {
                            player.DamageHistory.Add(this, DamageType.Health, vitalChange);
                        }
                    }

                    var verb = BoostValue >= 0 ? "restores" : "takes";
                    buffMessage = new GameMessageSystemChat($"The {Name} {verb} {vitalChange} points of your {BoosterEnum}.", ChatMessageType.Broadcast);
                }
                else
                {
                    buffMessage = new GameMessageSystemChat($"{Name} ({Guid}) contains invalid vital {BoosterEnum}", ChatMessageType.Broadcast);
                }
            }

            var soundEvent = new GameMessageSound(player.Guid, GetUseSound(), 1.0f);

            player.EnqueueBroadcast(soundEvent);

            player.Session.Network.EnqueueSend(buffMessage);

            player.TryConsumeFromInventoryWithNetworking(this, 1);
        }
Beispiel #4
0
        /// <summary>
        /// This is raised by Player.HandleActionUseItem.<para />
        /// The item should be in the players possession.
        ///
        /// The OnUse method for this class is to use a contract to add a tracked quest to our quest panel.
        /// This gives the player access to information about the quest such as starting and ending NPC locations,
        /// and shows our progress for kill tasks as well as any timing information such as when we can repeat the
        /// quest or how much longer we have to complete it in the case of at timed quest.   Og II
        /// </summary>
        public override void ActOnUse(WorldObject activator)
        {
            if (!(activator is Player player))
            {
                return;
            }

            if (UseCreateContractId == null)
            {
                player.EnchantmentManager.StartCooldown(this);

                if (SpellDID.HasValue)
                {
                    var spell = new Server.Entity.Spell((uint)SpellDID);

                    TryCastSpell(spell, player, this);
                }

                if ((GetProperty(PropertyBool.UnlimitedUse) ?? false) == false)
                {
                    player.TryConsumeFromInventoryWithNetworking(this, 1);
                }
            }
            else
            {
                ActOnUseContract(player);
            }
        }
Beispiel #5
0
        private static void MutateCaster_SpellDID(WorldObject wo, TreasureDeath profile)
        {
            var firstSpell = CasterSlotSpells.Roll(wo);

            var spellLevels = SpellLevelProgression.GetSpellLevels(firstSpell);

            if (spellLevels == null)
            {
                log.Error($"MutateCaster_SpellDID: couldn't find {firstSpell}");
                return;
            }

            if (spellLevels.Count != 8)
            {
                log.Error($"MutateCaster_SpellDID: found {spellLevels.Count} spell levels for {firstSpell}, expected 8");
                return;
            }

            var spellLevel = SpellLevelChance.Roll(profile.Tier);

            wo.SpellDID = (uint)spellLevels[spellLevel - 1];

            var spell = new Server.Entity.Spell(wo.SpellDID.Value);

            var castableMod = CasterSlotSpells.IsOrb(wo) ? 5.0f : 2.5f;

            wo.ItemManaCost = (int)(spell.BaseMana * castableMod);

            wo.ItemUseable = Usable.SourceWieldedTargetRemoteNeverWalk;
        }
Beispiel #6
0
        private static void MutateCaster_SpellDID(WorldObject wo, TreasureDeath profile)
        {
            var firstSpell = CasterSlotSpells.Roll(wo);

            var spellLevels = SpellLevelProgression.GetSpellLevels(firstSpell);

            if (spellLevels == null)
            {
                log.Error($"MutateCaster_SpellDID: couldn't find {firstSpell}");
                return;
            }

            if (spellLevels.Count != 8)
            {
                log.Error($"MutateCaster_SpellDID: found {spellLevels.Count} spell levels for {firstSpell}, expected 8");
                return;
            }

            int minSpellLevel = GetLowSpellTier(profile.Tier);
            int maxSpellLevel = GetHighSpellTier(profile.Tier);

            var spellLevel = ThreadSafeRandom.Next(minSpellLevel, maxSpellLevel);

            wo.SpellDID = (uint)spellLevels[spellLevel - 1];

            var spell = new Server.Entity.Spell(wo.SpellDID.Value);

            wo.ItemManaCost = (int)spell.BaseMana * 5;

            wo.ItemUseable = Usable.SourceWieldedTargetRemoteNeverWalk;
        }
Beispiel #7
0
 private void SetEphemeralValues()
 {
     if (SpellDID != null)
     {
         Spell = new Server.Entity.Spell(SpellDID.Value, false);
     }
 }
        private static void AssignMagic(WorldObject wo, TreasureDeath profile, TreasureRoll roll, bool isArmor = false)
        {
            int numSpells = 0;

            if (roll == null)
            {
                // previous method
                if (!AssignMagic_Spells(wo, profile, isArmor, out numSpells))
                {
                    return;
                }
            }
            else
            {
                // new method
                if (!AssignMagic_New(wo, profile, roll, out numSpells))
                {
                    return;
                }
            }

            wo.UiEffects = UiEffects.Magical;

            var maxBaseMana = GetMaxBaseMana(wo);

            wo.ManaRate = CalculateManaRate(maxBaseMana);

            if (roll == null)
            {
                wo.ItemMaxMana = RollItemMaxMana(numSpells, profile.Tier);
                wo.ItemCurMana = wo.ItemMaxMana;

                wo.ItemSpellcraft = RollSpellcraft(wo);
                wo.ItemDifficulty = RollItemDifficulty(wo);
            }
            else
            {
                var maxSpellMana = maxBaseMana;

                if (wo.SpellDID != null)
                {
                    var spell = new Server.Entity.Spell(wo.SpellDID.Value);

                    var castableMana = (int)spell.BaseMana * 5;

                    if (castableMana > maxSpellMana)
                    {
                        maxSpellMana = castableMana;
                    }
                }

                wo.ItemMaxMana = RollItemMaxMana_New(wo, roll, maxSpellMana);
                wo.ItemCurMana = wo.ItemMaxMana;

                wo.ItemSpellcraft = RollSpellcraft(wo, roll);

                AddActivationRequirements(wo, roll);
            }
        }
Beispiel #9
0
        /// <summary>
        /// Perfroms additional set up of the spell projectile based on the spell id or its derived type.
        /// </summary>
        public void Setup(Server.Entity.Spell spell, ProjectileSpellType spellType)
        {
            Spell = spell;
            SpellType = spellType;

            InitPhysicsObj();

            // Runtime changes to default state
            ReportCollisions = true;
            Missile = true;
            AlignPath = true;
            PathClipped = true;
            IgnoreCollisions = false;

            if (!Spell.Name.Equals("Rolling Death"))
                Ethereal = false;

            if (SpellType == ProjectileSpellType.Bolt || SpellType == ProjectileSpellType.Streak
                || SpellType == ProjectileSpellType.Arc || SpellType == ProjectileSpellType.Volley || SpellType == ProjectileSpellType.Blast
                || WeenieClassId == 7276 || WeenieClassId == 7277 || WeenieClassId == 7279 || WeenieClassId == 7280)
            {
                PhysicsObj.DefaultScript = PlayScript.ProjectileCollision;
                PhysicsObj.DefaultScriptIntensity = 1.0f;
            }

            // Some wall spells don't have scripted collisions
            if (WeenieClassId == 7278 || WeenieClassId == 7281 || WeenieClassId == 7282 || WeenieClassId == 23144)
            {
                ScriptedCollision = false;
            }

            AllowEdgeSlide = false;

            // No need to send an ObjScale of 1.0f over the wire since that is the default value
            if (ObjScale == 1.0f)
                ObjScale = null;

            if (SpellType == ProjectileSpellType.Ring)
            {
                if (spell.Id == 3818)
                {
                    DefaultScriptId = (uint)PlayScript.Explode;
                    DefaultScriptIntensity = 1.0f;
                    ScriptedCollision = true;
                }
                else
                {
                    ScriptedCollision = false;
                }
            }
                
            // Whirling Blade spells get omega values and "align path" turned off which
            // creates the nice swirling animation
            if (WeenieClassId == 1636 || WeenieClassId == 7268 || WeenieClassId == 20979)
            {
                AlignPath = false;
                Omega = new Vector3(12.56637f, 0, 0);
            }
        }
Beispiel #10
0
        /// <summary>
        /// This is raised by Player.HandleActionUseItem.<para />
        /// The item should be in the players possession.
        ///
        /// The OnUse method for this class is to use a contract to add a tracked quest to our quest panel.
        /// This gives the player access to information about the quest such as starting and ending NPC locations,
        /// and shows our progress for kill tasks as well as any timing information such as when we can repeat the
        /// quest or how much longer we have to complete it in the case of at timed quest.   Og II
        /// </summary>
        public override void ActOnUse(WorldObject activator)
        {
            if (!(activator is Player player))
            {
                return;
            }

            if (player.IsBusy || player.Teleporting)
            {
                player.Session.Network.EnqueueSend(new GameEventWeenieError(player.Session, WeenieError.YoureTooBusy));
                return;
            }

            if (UseCreateContractId != null)
            {
                ActOnUseContract(player);
                return;
            }

            if (!string.IsNullOrWhiteSpace(UseSendsSignal))
            {
                player.CurrentLandblock?.EmitSignal(player, UseSendsSignal);
                return;
            }

            // handle rare gems
            if (RareUsesTimer)
            {
                var currentTime = Time.GetUnixTime();

                var timeElapsed = currentTime - player.LastRareUsedTimestamp;

                if (timeElapsed < RareTimer)
                {
                    // TODO: get retail message
                    var remainTime = (int)Math.Ceiling(RareTimer - timeElapsed);
                    player.Session.Network.EnqueueSend(new GameMessageSystemChat($"You may use another timed rare in {remainTime}s", ChatMessageType.Broadcast));
                    return;
                }

                player.LastRareUsedTimestamp = currentTime;

                // local broadcast usage
                player.EnqueueBroadcast(new GameMessageSystemChat($"{player.Name} used the rare item {Name}", ChatMessageType.Broadcast));
            }

            if (SpellDID.HasValue)
            {
                var spell = new Server.Entity.Spell((uint)SpellDID);

                TryCastSpell(spell, player, this);
            }

            if ((GetProperty(PropertyBool.UnlimitedUse) ?? false) == false)
            {
                player.TryConsumeFromInventoryWithNetworking(this, 1);
            }
        }
Beispiel #11
0
        public static ProjectileSpellType GetProjectileSpellType(uint spellID)
        {
            var spell = new Server.Entity.Spell(spellID);

            if (spell.Wcid == 0)
            {
                return(ProjectileSpellType.Undef);
            }

            if (spell.NumProjectiles == 1)
            {
                if (spell.Category >= SpellCategory.AcidStreak && spell.Category <= SpellCategory.SlashingStreak ||
                    spell.Category == SpellCategory.NetherStreak || spell.Category == SpellCategory.Fireworks)
                {
                    return(ProjectileSpellType.Streak);
                }

                else if (spell.NonTracking)
                {
                    return(ProjectileSpellType.Arc);
                }

                else
                {
                    return(ProjectileSpellType.Bolt);
                }
            }

            if (spell.Category >= SpellCategory.AcidRing && spell.Category <= SpellCategory.SlashingRing || spell.SpreadAngle == 360)
            {
                return(ProjectileSpellType.Ring);
            }

            if (spell.Category >= SpellCategory.AcidBurst && spell.Category <= SpellCategory.SlashingBurst ||
                spell.Category == SpellCategory.NetherDamageOverTimeRaising3)
            {
                return(ProjectileSpellType.Blast);
            }

            // 1481 - Flaming Missile Volley
            if (spell.Category >= SpellCategory.AcidVolley && spell.Category <= SpellCategory.BladeVolley || spell.Name.Contains("Volley"))
            {
                return(ProjectileSpellType.Volley);
            }

            if (spell.Category >= SpellCategory.AcidWall && spell.Category <= SpellCategory.SlashingWall)
            {
                return(ProjectileSpellType.Wall);
            }

            if (spell.Category >= SpellCategory.AcidStrike && spell.Category <= SpellCategory.SlashingStrike)
            {
                return(ProjectileSpellType.Strike);
            }

            return(ProjectileSpellType.Undef);
        }
Beispiel #12
0
        public static ProjectileSpellType GetProjectileSpellType(uint spellID)
        {
            var spell = new Server.Entity.Spell(spellID);

            if (spell.Wcid == 0)
            {
                return(ProjectileSpellType.Undef);
            }

            // TODO: improve readability
            if ((spell.Wcid >= 7262 && spell.Wcid <= 7268) || (spellID >= 5345 && spellID <= 5348) || (spellID >= 5357 && spellID <= 5360))
            {
                return(ProjectileSpellType.Streak);
            }
            else if (spell.Wcid >= 7269 && spell.Wcid <= 7275 || spell.Wcid == 43233 || spellID == 6320)
            {
                return(ProjectileSpellType.Ring);
            }
            else if (spell.Wcid >= 7276 && spell.Wcid <= 7282 || spell.Wcid == 23144)
            {
                return(ProjectileSpellType.Wall);
            }
            else if ((spell.Wcid >= 20973 && spell.Wcid <= 20979) || (spellID >= 5362 && spellID <= 5369))
            {
                return(ProjectileSpellType.Arc);
            }
            else if (spell.Wcid == 1499 || spell.Wcid == 1503 || (spell.Wcid >= 1633 && spell.Wcid <= 1667) || (spellID >= 5395 && spellID <= 5402) || (spellID >= 5544 && spellID <= 5551))
            {
                if (spell.SpreadAngle > 0)
                {
                    return(ProjectileSpellType.Blast);
                }
                else if (spell.DimsOrigin.X > 1)
                {
                    return(ProjectileSpellType.Volley);
                }
                else
                {
                    return(ProjectileSpellType.Bolt);
                }
            }

            if (spell.Name.Equals("Rolling Death"))
            {
                return(ProjectileSpellType.Wall);    // ??
            }
            if (spell.School == MagicSchool.VoidMagic)
            {
                return(ProjectileSpellType.Bolt);
            }

            return(ProjectileSpellType.Undef);
        }
Beispiel #13
0
        private void SetEphemeralValues()
        {
            if (SpellDID != null)
            {
                Spell = new Server.Entity.Spell(SpellDID.Value, false);
            }

            if (Spell != null)
            {
                LongDesc = $"Inscribed spell: {Spell.Name}\n{Spell.Description}";
            }
            Use = "Use this item to attempt to learn its spell.";
        }
Beispiel #14
0
        /// <summary>
        /// Perfroms additional set up of the spell projectile based on the spell id or its derived type.
        /// </summary>
        /// <param name="spellId"></param>
        public void Setup(uint spellId)
        {
            Spell     = new Server.Entity.Spell(spellId);
            SpellType = GetProjectileSpellType(spellId);

            InitPhysicsObj();

            // Runtime changes to default state
            ReportCollisions = true;
            Missile          = true;
            AlignPath        = true;
            PathClipped      = true;
            Ethereal         = false;
            IgnoreCollisions = false;

            if (SpellType == ProjectileSpellType.Bolt || SpellType == ProjectileSpellType.Streak ||
                SpellType == ProjectileSpellType.Arc || SpellType == ProjectileSpellType.Volley || SpellType == ProjectileSpellType.Blast ||
                WeenieClassId == 7276 || WeenieClassId == 7277 || WeenieClassId == 7279 || WeenieClassId == 7280)
            {
                PhysicsObj.DefaultScript          = ACE.Entity.Enum.PlayScript.ProjectileCollision;
                PhysicsObj.DefaultScriptIntensity = 1.0f;
            }

            // Some wall spells don't have scripted collisions
            if (WeenieClassId == 7278 || WeenieClassId == 7281 || WeenieClassId == 7282 || WeenieClassId == 23144)
            {
                ScriptedCollision = false;
            }

            AllowEdgeSlide = false;
            // No need to send an ObjScale of 1.0f over the wire since that is the default value
            if (ObjScale == 1.0f)
            {
                ObjScale = null;
            }

            if (SpellType == ProjectileSpellType.Ring)
            {
                ScriptedCollision = false;
            }

            // Whirling Blade spells get omega values and "align path" turned off which
            // creates the nice swirling animation
            if (WeenieClassId == 1636 || WeenieClassId == 7268 || WeenieClassId == 20979)
            {
                AlignPath = false;
                Omega     = new AceVector3(12.56637f, 0f, 0f);
            }
        }
Beispiel #15
0
        /// <summary>
        /// Called on player login
        /// If a player has any skills trained that require updates from ACE-World-16-Patches,
        /// ensure these updates are installed, and if they aren't, send a helpful message to player with instructions for installation
        /// </summary>
        public void HandleDBUpdates()
        {
            // dirty fighting
            var dfSkill = GetCreatureSkill(Skill.DirtyFighting);

            if (dfSkill.AdvancementClass >= SkillAdvancementClass.Trained)
            {
                foreach (var spellID in SpellExtensions.DirtyFightingSpells)
                {
                    var spell = new Server.Entity.Spell(spellID);
                    if (spell.NotFound)
                    {
                        var actionChain = new ActionChain();
                        actionChain.AddDelaySeconds(3.0f);
                        actionChain.AddAction(this, () =>
                        {
                            Session.Network.EnqueueSend(new GameMessageSystemChat("To install Dirty Fighting, please apply the latest patches from https://github.com/ACEmulator/ACE-World-16PY-Patches", ChatMessageType.Broadcast));
                        });
                        actionChain.EnqueueChain();
                    }
                    break;  // performance improvement: only check first spell
                }
            }

            // void magic
            var voidSkill = GetCreatureSkill(Skill.VoidMagic);

            if (voidSkill.AdvancementClass >= SkillAdvancementClass.Trained)
            {
                foreach (var spellID in SpellExtensions.VoidMagicSpells)
                {
                    var spell = new Server.Entity.Spell(spellID);
                    if (spell.NotFound)
                    {
                        var actionChain = new ActionChain();
                        actionChain.AddDelaySeconds(3.0f);
                        actionChain.AddAction(this, () =>
                        {
                            Session.Network.EnqueueSend(new GameMessageSystemChat("To install Void Magic, please apply the latest patches from https://github.com/ACEmulator/ACE-World-16PY-Patches", ChatMessageType.Broadcast));
                        });
                        actionChain.EnqueueChain();
                    }
                    break;  // performance improvement: only check first spell (measured 102ms to check 75 uncached void spells)
                }
            }
        }
        /// <summary>
        /// Returns the maximum BaseMana from the spells in item's spellbook
        /// </summary>
        public static int GetMaxBaseMana(WorldObject wo)
        {
            var maxBaseMana = 0;

            if (wo.Biota.PropertiesSpellBook != null)
            {
                foreach (var spellId in wo.Biota.PropertiesSpellBook.Keys)
                {
                    var spell = new Server.Entity.Spell(spellId);

                    if (spell.BaseMana > maxBaseMana)
                    {
                        maxBaseMana = (int)spell.BaseMana;
                    }
                }
            }
            return(maxBaseMana);
        }
Beispiel #17
0
        public static ProjectileSpellType GetProjectileSpellType(uint spellID)
        {
            var spell = new Server.Entity.Spell(spellID);

            if (spell.Wcid == 0)
                return ProjectileSpellType.Undef;

            if (spell.NumProjectiles == 1)
            {
                if (spell.Category >= SpellCategory.AcidStreak && spell.Category <= SpellCategory.SlashingStreak ||
                         spell.Category == SpellCategory.NetherStreak || spell.Category == SpellCategory.Fireworks)
                    return ProjectileSpellType.Streak;

                else if (spell.NonTracking)
                    return ProjectileSpellType.Arc;

                else if (spell.Name.Contains("Rolling Death"))
                    return ProjectileSpellType.Wall;

                else
                    return ProjectileSpellType.Bolt;
            }

            if (spell.Category >= SpellCategory.AcidRing && spell.Category <= SpellCategory.SlashingRing || spell.SpreadAngle == 360)
                return ProjectileSpellType.Ring;

            if (spell.Category >= SpellCategory.AcidBurst && spell.Category <= SpellCategory.SlashingBurst ||
                spell.Category == SpellCategory.NetherDamageOverTimeRaising3)
                return ProjectileSpellType.Blast;

            // 1481 - Flaming Missile Volley
            if (spell.Category >= SpellCategory.AcidVolley && spell.Category <= SpellCategory.BladeVolley || spell.Name.Contains("Volley"))
                return ProjectileSpellType.Volley;

            if (spell.Category >= SpellCategory.AcidWall && spell.Category <= SpellCategory.SlashingWall)
                return ProjectileSpellType.Wall;

            if (spell.Category >= SpellCategory.AcidStrike && spell.Category <= SpellCategory.SlashingStrike)
                return ProjectileSpellType.Strike;

            return ProjectileSpellType.Undef;
        }
Beispiel #18
0
        /// <summary>
        /// This is raised by Player.HandleActionUseItem.<para />
        /// The item should be in the players possession.
        ///
        /// The OnUse method for this class is to use a contract to add a tracked quest to our quest panel.
        /// This gives the player access to information about the quest such as starting and ending NPC locations,
        /// and shows our progress for kill tasks as well as any timing information such as when we can repeat the
        /// quest or how much longer we have to complete it in the case of at timed quest.   Og II
        /// </summary>
        public override void ActOnUse(WorldObject activator)
        {
            if (!(activator is Player player))
            {
                return;
            }

            if (player.IsBusy || player.Teleporting)
            {
                player.Session.Network.EnqueueSend(new GameEventWeenieError(player.Session, WeenieError.YoureTooBusy));
                return;
            }

            if (!string.IsNullOrWhiteSpace(UseSendsSignal))
            {
                player.CurrentLandblock?.EmitSignal(player, UseSendsSignal);
                return;
            }

            // handle rare gems
            if (RareUsesTimer)
            {
                var currentTime = Time.GetUnixTime();

                var timeElapsed = currentTime - player.LastRareUsedTimestamp;

                if (timeElapsed < RareTimer)
                {
                    // TODO: get retail message
                    var remainTime = (int)Math.Ceiling(RareTimer - timeElapsed);
                    player.Session.Network.EnqueueSend(new GameMessageSystemChat($"You may use another timed rare in {remainTime}s", ChatMessageType.Broadcast));
                    return;
                }

                player.LastRareUsedTimestamp = currentTime;

                // local broadcast usage
                player.EnqueueBroadcast(new GameMessageSystemChat($"{player.Name} used the rare item {Name}", ChatMessageType.Broadcast));
            }

            if (SpellDID.HasValue)
            {
                var spell = new Server.Entity.Spell((uint)SpellDID);

                TryCastSpell(spell, player, this);
            }

            if (UseCreateContractId.HasValue && UseCreateContractId > 0)
            {
                if (!player.ContractManager.Add(UseCreateContractId.Value))
                {
                    return;
                }
                else // this wasn't in retail, but the lack of feedback when using a contract gem just seems jarring so...
                {
                    player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} accepted. Click on the quill icon in the lower right corner to open your contract tab to view your active contracts.", ChatMessageType.Broadcast));
                }
            }

            if ((GetProperty(PropertyBool.UnlimitedUse) ?? false) == false)
            {
                player.TryConsumeFromInventoryWithNetworking(this, 1);
            }
        }
Beispiel #19
0
        /// <summary>
        /// Method used to perform the animation, sound, and vital update on consumption of food or potions
        /// </summary>
        /// <param name="consumableName">Name of the consumable</param>
        /// <param name="sound">Either Sound.Eat1 or Sound.Drink1</param>
        /// <param name="buffType">ConsumableBuffType.Spell,ConsumableBuffType.Health,ConsumableBuffType.Stamina,ConsumableBuffType.Mana</param>
        /// <param name="boostAmount">Amount the Vital is boosted by; can be null, if buffType = ConsumableBuffType.Spell</param>
        /// <param name="spellDID">Id of the spell cast by the consumable; can be null, if buffType != ConsumableBuffType.Spell</param>
        public void ApplyComsumable(string consumableName, Sound sound, ConsumableBuffType buffType, uint?boostAmount, uint?spellDID)
        {
            uint spellId = spellDID ?? 0;

            GameMessageSystemChat buffMessage;
            MotionCommand         motionCommand;

            if (sound == Sound.Eat1)
            {
                motionCommand = MotionCommand.Eat;
            }
            else
            {
                motionCommand = MotionCommand.Drink;
            }

            var soundEvent = new GameMessageSound(Guid, sound, 1.0f);
            var motion     = new UniversalMotion(MotionStance.NonCombat, new MotionItem(motionCommand));

            EnqueueBroadcastMotion(motion);

            if (buffType == ConsumableBuffType.Spell)
            {
                bool result = false;
                if (spellId != 0)
                {
                    result = CreateSingleSpell(spellId);
                }

                var spell = new Server.Entity.Spell(spellId);

                if (!result)
                {
                    buffMessage = new GameMessageSystemChat($"Consuming {consumableName} attempted to apply a spell not yet fully implemented.", ChatMessageType.System);
                }
                else
                {
                    buffMessage = new GameMessageSystemChat($"{consumableName} applies {spell.Name} on you.", ChatMessageType.Craft);
                }
            }
            else
            {
                CreatureVital creatureVital;
                string        vitalName;

                // Null check for safety
                if (boostAmount == null)
                {
                    boostAmount = 0;
                }

                switch (buffType)
                {
                case ConsumableBuffType.Health:
                    creatureVital = Health;
                    vitalName     = "Health";
                    break;

                case ConsumableBuffType.Mana:
                    creatureVital = Mana;
                    vitalName     = "Mana";
                    break;

                default:
                    creatureVital = Stamina;
                    vitalName     = "Stamina";
                    break;
                }

                var vitalChange = UpdateVitalDelta(creatureVital, (uint)boostAmount);

                buffMessage = new GameMessageSystemChat($"You regain {vitalChange} {vitalName}.", ChatMessageType.Craft);
            }

            Session.Network.EnqueueSend(soundEvent, buffMessage);

            // Wait for animation
            var motionChain           = new ActionChain();
            var motionTable           = DatManager.PortalDat.ReadFromDat <MotionTable>((uint)MotionTableId);
            var motionAnimationLength = motionTable.GetAnimationLength(MotionCommand.Eat);

            motionChain.AddDelaySeconds(motionAnimationLength);

            // Return to standing position after the animation delay
            motionChain.AddAction(this, () => EnqueueBroadcastMotion(new UniversalMotion(MotionStance.NonCombat)));
            motionChain.EnqueueChain();
        }
Beispiel #20
0
        /// <summary>
        /// Activates the object linked to a pressure plate
        /// </summary>
        public override void Activate(WorldObject activator)
        {
            if (!Active)
            {
                return;
            }

            var player = activator as Player;

            if (player == null)
            {
                return;
            }

            // prevent continuous event stream
            var currentTime = DateTime.UtcNow;

            if (currentTime < LastUseTime + TimeSpan.FromSeconds(2))
            {
                return;
            }

            // TODO: this should simply be forwarding the activation event
            // along to the activation target...

            // ensure activation target
            if (ActivationTarget == 0)
            {
                return;
            }

            var target = CurrentLandblock?.GetObject(new ObjectGuid(ActivationTarget));

            if (target == null)
            {
                Console.WriteLine($"{Name}.OnCollideObject({activator.Name}): couldn't find activation target {ActivationTarget:X8}");
                return;
            }

            LastUseTime = currentTime;
            //Console.WriteLine($"{activator.Name} activated {Name}");

            // activate pressure plate sound
            player.EnqueueBroadcast(new GameMessageSound(player.Guid, (Sound)UseSound));

            // activate target -

            // default use action
            if (target.ActivationResponse.HasFlag(ActivationResponse.Use))
            {
                target.ActOnUse(this);
            }

            // perform motion animation - rarely used (only 4 instances in PY16 db)
            if (target.ActivationResponse.HasFlag(ActivationResponse.Animate))
            {
                var motion = new Motion(target, ActivationAnimation);
                target.EnqueueBroadcastMotion(motion);
            }

            // send chat text - rarely used (only 8 instances in PY16 db)
            if (target.ActivationResponse.HasFlag(ActivationResponse.Talk))
            {
                // todo: verify the format of this message
                player.Session.Network.EnqueueSend(new GameMessageSystemChat(ActivationTalk, ChatMessageType.Broadcast));
                //target.EnqueueBroadcast(new GameMessageSystemChat(ActivationTalk, ChatMessageType.Broadcast));
            }

            // perform activation emote
            if (target.ActivationResponse.HasFlag(ActivationResponse.Emote))
            {
                target.EmoteManager.OnActivation(player);
            }

            // cast a spell on the player (spell traps)
            if (target.ActivationResponse.HasFlag(ActivationResponse.CastSpell))
            {
                if (target.SpellDID != null)
                {
                    var spell = new Server.Entity.Spell((uint)target.SpellDID);
                    target.TryCastSpell(spell, player);
                }
            }

            // call to generator to spawn new object
            if (target.ActivationResponse.HasFlag(ActivationResponse.Generate))
            {
                if (target.IsGenerator)
                {
                    target.SelectProfilesMax();
                }
            }
        }
Beispiel #21
0
        /// <summary>
        /// This is raised by Player.HandleActionUseItem.<para />
        /// The item should be in the players possession.
        ///
        /// The OnUse method for this class is to use a contract to add a tracked quest to our quest panel.
        /// This gives the player access to information about the quest such as starting and ending NPC locations,
        /// and shows our progress for kill tasks as well as any timing information such as when we can repeat the
        /// quest or how much longer we have to complete it in the case of at timed quest.   Og II
        /// </summary>
        public override void UseItem(Player player)
        {
            // TODO: verify use requirements

            if (UseCreateContractId == null)
            {
                //var spell = new Spell((uint)SpellDID);

                /*
                 * //These if statements are to catch spells with an apostrophe in the dat file which throws off the client in reading it from the dat.
                 * if (spell.MetaSpellId == 3810)
                 *  castMessage = "The gem casts Asheron's Benediction on you";
                 * if (spell.MetaSpellId == 3811)
                 *  castMessage = "The gem casts Blackmoor's Favor on you";
                 * if (spell.MetaSpellId == 3953)
                 *  castMessage = "The gem casts Carraida's Benediction on you";
                 * if (spell.MetaSpellId == 4024)
                 *  castMessage = "The gem casts Asheron's Lesser Benediction on you";
                 */

                if (SpellDID.HasValue)
                {
                    var spell = new Server.Entity.Spell((uint)SpellDID);

                    TryCastSpell(spell, player, this);
                }

                if ((GetProperty(PropertyBool.UnlimitedUse) ?? false) == false)
                {
                    player.TryConsumeFromInventoryWithNetworking(this, 1);
                }

                player.SendUseDoneEvent();
                return;
            }

            ContractTracker contractTracker = new ContractTracker((uint)UseCreateContractId, player.Guid.Full)
            {
                Stage                = 0,
                TimeWhenDone         = 0,
                TimeWhenRepeats      = 0,
                DeleteContract       = 0,
                SetAsDisplayContract = 1
            };

            if (CooldownId != null && player.LastUseTracker.TryGetValue(CooldownId.Value, out var lastUse))
            {
                var timeRemaining = lastUse.AddSeconds(CooldownDuration ?? 0.00).Subtract(DateTime.Now);
                if (timeRemaining.Seconds > 0)
                {
                    ChatPacket.SendServerMessage(player.Session, "You cannot use another contract for " + timeRemaining.Seconds + " seconds", ChatMessageType.Broadcast);
                    player.SendUseDoneEvent();
                    return;
                }
            }

            // We need to see if we are tracking this quest already.   Also, I cannot be used on world, so I must have a container id
            if (!player.TrackedContracts.ContainsKey((uint)UseCreateContractId) && ContainerId != null)
            {
                player.TrackedContracts.Add((uint)UseCreateContractId, contractTracker);

                // This will track our use for each contract using the shared cooldown server side.
                if (CooldownId != null)
                {
                    // add or update.
                    if (!player.LastUseTracker.ContainsKey(CooldownId.Value))
                    {
                        player.LastUseTracker.Add(CooldownId.Value, DateTime.Now);
                    }
                    else
                    {
                        player.LastUseTracker[CooldownId.Value] = DateTime.Now;
                    }
                }

                GameEventSendClientContractTracker contractMsg = new GameEventSendClientContractTracker(player.Session, contractTracker);
                player.Session.Network.EnqueueSend(contractMsg);
                ChatPacket.SendServerMessage(player.Session, "You just added " + contractTracker.ContractDetails.ContractName, ChatMessageType.Broadcast);

                // TODO: Add sending the 02C2 message UpdateEnchantment.   They added a second use to this existing system
                // so they could show the delay on the client side - it is not really an enchantment but the they overloaded the use. Og II
                // Thanks Slushnas for letting me know about this as well as an awesome pcap that shows it all in action.

                // TODO: there is a lot of work to do here.   I am stubbing this in for now to send the right message.   Lots of magic numbers at the moment.
                Debug.Assert(CooldownId != null, "CooldownId != null");
                Debug.Assert(CooldownDuration != null, "CooldownDuration != null");
                //const ushort layer = 0x10000; // FIXME: we need to track how many layers of the exact same spell we have in effect.
                const ushort layer = 1;
                //const uint spellCategory = 0x8000; // FIXME: Not sure where we get this from
                var spellBase = new SpellBase(0, CooldownDuration.Value, 0, -666);
                // cooldown not being used in network packet?
                var gem = new Enchantment(player, player.Guid.Full, spellBase, spellBase.Duration, layer, /*CooldownId.Value,*/ EnchantmentMask.Cooldown);
                player.Session.Network.EnqueueSend(new GameEventMagicUpdateEnchantment(player.Session, gem));

                // Ok this was not known to us, so we used the contract - now remove it from inventory.
                // HandleActionRemoveItemFromInventory is has it's own action chain.
                player.TryConsumeFromInventoryWithNetworking(this, 1);
            }
            else
            {
                ChatPacket.SendServerMessage(player.Session, "You already have this quest tracked: " + contractTracker.ContractDetails.ContractName, ChatMessageType.Broadcast);
            }

            // No mater any condition we need to send the use done event to clear the hour glass from the client.
            player.SendUseDoneEvent();
        }
Beispiel #22
0
        public void ActOnUse(WorldObject activator, bool confirmed)
        {
            if (!(activator is Player player))
            {
                return;
            }

            if (player.IsBusy || player.Teleporting)
            {
                player.Session.Network.EnqueueSend(new GameEventWeenieError(player.Session, WeenieError.YoureTooBusy));
                return;
            }

            if (!string.IsNullOrWhiteSpace(UseSendsSignal))
            {
                player.CurrentLandblock?.EmitSignal(player, UseSendsSignal);
                return;
            }

            // handle rare gems
            if (RareId != null && player.GetCharacterOption(CharacterOption.ConfirmUseOfRareGems) && !confirmed)
            {
                var msg     = $"Are you sure you want to use {Name}?";
                var confirm = new Confirmation_Custom(player.Guid, () => ActOnUse(activator, true));
                player.ConfirmationManager.EnqueueSend(confirm, msg);
                return;
            }

            if (RareUsesTimer)
            {
                var currentTime = Time.GetUnixTime();

                var timeElapsed = currentTime - player.LastRareUsedTimestamp;

                if (timeElapsed < RareTimer)
                {
                    // TODO: get retail message
                    var remainTime = (int)Math.Ceiling(RareTimer - timeElapsed);
                    player.Session.Network.EnqueueSend(new GameMessageSystemChat($"You may use another timed rare in {remainTime}s", ChatMessageType.Broadcast));
                    return;
                }

                player.LastRareUsedTimestamp = currentTime;

                // local broadcast usage
                player.EnqueueBroadcast(new GameMessageSystemChat($"{player.Name} used the rare item {Name}", ChatMessageType.Broadcast));
            }

            if (SpellDID.HasValue)
            {
                var spell = new Server.Entity.Spell((uint)SpellDID);

                TryCastSpell(spell, player, this);
            }

            if (UseCreateContractId > 0)
            {
                if (!player.ContractManager.Add(UseCreateContractId.Value))
                {
                    return;
                }
                else // this wasn't in retail, but the lack of feedback when using a contract gem just seems jarring so...
                {
                    player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} accepted. Click on the quill icon in the lower right corner to open your contract tab to view your active contracts.", ChatMessageType.Broadcast));
                }
            }

            if (UseCreateItem > 0)
            {
                //if (DatabaseManager.World.GetCachedWeenie(UseCreateItem.Value) is null)
                //{
                //    player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, "Unable to create object, WCID is not in database !")); // custom error
                //    return;
                //}

                var playerFreeInventorySlots = player.GetFreeInventorySlots();
                var playerFreeContainerSlots = player.GetFreeContainerSlots();
                var playerAvailableBurden    = player.GetAvailableBurden();

                var playerOutOfInventorySlots    = false;
                var playerOutOfContainerSlots    = false;
                var playerExceedsAvailableBurden = false;

                var amount = UseCreateQuantity ?? 1;

                var itemStacks = player.PreCheckItem(UseCreateItem.Value, amount, playerFreeContainerSlots, playerFreeInventorySlots, playerAvailableBurden, out var itemEncumberance, out bool itemRequiresBackpackSlot);

                if (itemRequiresBackpackSlot)
                {
                    playerFreeContainerSlots -= itemStacks;
                    playerAvailableBurden    -= itemEncumberance;

                    playerOutOfContainerSlots = playerFreeContainerSlots < 0;
                }
                else
                {
                    playerFreeInventorySlots -= itemStacks;
                    playerAvailableBurden    -= itemEncumberance;

                    playerOutOfInventorySlots = playerFreeInventorySlots < 0;
                }

                playerExceedsAvailableBurden = playerAvailableBurden < 0;

                if (playerOutOfInventorySlots || playerOutOfContainerSlots || playerExceedsAvailableBurden)
                {
                    if (playerExceedsAvailableBurden)
                    {
                        player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, "You are too encumbered to use that!"));
                    }
                    else if (playerOutOfInventorySlots)
                    {
                        player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, "You do not have enough pack space to use that!"));
                    }
                    else //if (playerOutOfContainerSlots)
                    {
                        player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, "You do not have enough container slots to use that!"));
                    }
                    return;
                }

                if (itemStacks > 0)
                {
                    while (amount > 0)
                    {
                        var item = WorldObjectFactory.CreateNewWorldObject(UseCreateItem.Value);

                        if (item is Stackable)
                        {
                            // amount contains a max stack
                            if (item.MaxStackSize <= amount)
                            {
                                item.SetStackSize(item.MaxStackSize);
                                amount -= item.MaxStackSize.Value;
                            }
                            else // not a full stack
                            {
                                item.SetStackSize(amount);
                                amount -= amount;
                            }
                        }
                        else
                        {
                            amount -= 1;
                        }

                        player.TryCreateInInventoryWithNetworking(item);
                    }
                }
                else
                {
                    player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, $"Unable to use {Name} at this time!"));
                    return;
                }
            }

            if ((GetProperty(PropertyBool.UnlimitedUse) ?? false) == false)
            {
                player.TryConsumeFromInventoryWithNetworking(this, 1);
            }
        }
Beispiel #23
0
        /// <summary>
        /// Method used to perform the animation, sound, and vital update on consumption of food or potions
        /// </summary>
        /// <param name="consumableName">Name of the consumable</param>
        /// <param name="sound">Either Sound.Eat1 or Sound.Drink1</param>
        /// <param name="buffType">ConsumableBuffType.Spell,ConsumableBuffType.Health,ConsumableBuffType.Stamina,ConsumableBuffType.Mana</param>
        /// <param name="boostAmount">Amount the Vital is boosted by; can be null, if buffType = ConsumableBuffType.Spell</param>
        /// <param name="spellDID">Id of the spell cast by the consumable; can be null, if buffType != ConsumableBuffType.Spell</param>
        public void ApplyConsumable(string consumableName, Sound sound, ConsumableBuffType buffType, uint?boostAmount, uint?spellDID)
        {
            MotionCommand motionCommand;

            if (sound == Sound.Eat1)
            {
                motionCommand = MotionCommand.Eat;
            }
            else
            {
                motionCommand = MotionCommand.Drink;
            }

            // start the eat/drink motion
            var motion = new Motion(MotionStance.NonCombat, motionCommand);

            EnqueueBroadcastMotion(motion);

            var motionTable = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId);
            var animTime    = motionTable.GetAnimationLength(CurrentMotionState.Stance, motionCommand, MotionCommand.Ready);

            var actionChain = new ActionChain();

            actionChain.AddDelaySeconds(animTime);

            actionChain.AddAction(this, () =>
            {
                GameMessageSystemChat buffMessage;

                if (buffType == ConsumableBuffType.Spell)
                {
                    bool result = false;

                    uint spellId = spellDID ?? 0;

                    if (spellId != 0)
                    {
                        result = CreateSingleSpell(spellId);
                    }

                    if (result)
                    {
                        var spell   = new Server.Entity.Spell(spellId);
                        buffMessage = new GameMessageSystemChat($"{consumableName} casts {spell.Name} on you.", ChatMessageType.Magic);
                    }
                    else
                    {
                        buffMessage = new GameMessageSystemChat($"Consuming {consumableName} attempted to apply a spell not yet fully implemented.", ChatMessageType.System);
                    }
                }
                else
                {
                    CreatureVital creatureVital;
                    string vitalName;

                    // Null check for safety
                    if (boostAmount == null)
                    {
                        boostAmount = 0;
                    }

                    switch (buffType)
                    {
                    case ConsumableBuffType.Health:
                        creatureVital = Health;
                        vitalName     = "Health";
                        break;

                    case ConsumableBuffType.Mana:
                        creatureVital = Mana;
                        vitalName     = "Mana";
                        break;

                    default:
                        creatureVital = Stamina;
                        vitalName     = "Stamina";
                        break;
                    }

                    var vitalChange = UpdateVitalDelta(creatureVital, (uint)boostAmount);
                    if (vitalName == "Health")
                    {
                        DamageHistory.OnHeal((uint)vitalChange);
                        if (Fellowship != null)
                        {
                            Fellowship.OnVitalUpdate(this);
                        }
                    }

                    buffMessage = new GameMessageSystemChat($"You regain {vitalChange} {vitalName}.", ChatMessageType.Craft);
                }

                var soundEvent = new GameMessageSound(Guid, sound, 1.0f);
                EnqueueBroadcast(soundEvent);

                Session.Network.EnqueueSend(buffMessage);

                // return to original stance
                var returnStance = new Motion(CurrentMotionState.Stance);
                EnqueueBroadcastMotion(returnStance);
            });

            actionChain.EnqueueChain();
        }