Пример #1
0
        /// <summary>
        /// Public method for adding a new skill by spending skill credits.
        /// </summary>
        /// <remarks>
        ///  The client will throw up more then one train skill dialog and the user has the chance to spend twice.
        /// </remarks>
        public void HandleActionTrainSkill(Skill skill, int creditsSpent)
        {
            if (AvailableSkillCredits >= creditsSpent)
            {
                // attempt to train the specified skill
                bool trainNewSkill = TrainSkill(skill, creditsSpent);

                // create an update to send to the client
                var currentCredits = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.AvailableSkillCredits, AvailableSkillCredits ?? 0);

                // as long as the skill is sent, the train new triangle button on the client will not lock up.
                // Sending Skill.None with status untrained worked in test
                var trainSkillUpdate = new GameMessagePrivateUpdateSkill(this, Skill.None, SkillAdvancementClass.Untrained, 0, 0, 0);
                // create a string placeholder for the correct after
                string trainSkillMessageText;

                // if the skill has already been trained or we do not have enough credits, then trainNewSkill be set false
                if (trainNewSkill)
                {
                    // replace the trainSkillUpdate message with the correct skill assignment:
                    var creatureSkill = GetCreatureSkill(skill);
                    trainSkillUpdate      = new GameMessagePrivateUpdateSkill(this, creatureSkill);
                    trainSkillMessageText = $"{skill.ToSentence()} trained. You now have {AvailableSkillCredits} credits available.";
                }
                else
                {
                    trainSkillMessageText = $"Failed to train {skill.ToSentence()}! You now have {AvailableSkillCredits} credits available.";
                }

                // create the final game message and send to the client
                var message = new GameMessageSystemChat(trainSkillMessageText, ChatMessageType.Advancement);
                Session.Network.EnqueueSend(trainSkillUpdate, currentCredits, message);
            }
        }
Пример #2
0
        /// <summary>
        /// Broadcasts the player death animation, updates vitae, and sends network messages for player death
        /// Queues the action to call TeleportOnDeath and enter portal space soon
        /// </summary>
        protected override void Die(WorldObject lastDamager, WorldObject topDamager)
        {
            UpdateVital(Health, 0);
            NumDeaths++;

            // killer = top damager for looting rights
            if (topDamager != null)
            {
                Killer = topDamager.Guid.Full;
            }

            // broadcast death animation
            var deathAnim = new Motion(MotionStance.NonCombat, MotionCommand.Dead);

            EnqueueBroadcastMotion(deathAnim);

            // killer death message = last damager
            var killerMsg           = lastDamager != null ? " to " + lastDamager.Name : "";
            var currentDeathMessage = $"died{killerMsg}.";

            // create network messages for player death
            var msgHealthUpdate = new GameMessagePrivateUpdateAttribute2ndLevel(this, Vital.Health, 0);

            // TODO: death sounds? seems to play automatically in client
            // var msgDeathSound = new GameMessageSound(Guid, Sound.Death1, 1.0f);
            var msgNumDeaths = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.NumDeaths, NumDeaths ?? 0);

            // send network messages for player death
            Session.Network.EnqueueSend(msgHealthUpdate, msgNumDeaths);

            // update vitae
            // players who died in a PKLite fight do not accrue vitae
            var pkLiteKiller = GetKiller_PKLite();

            if (pkLiteKiller == null)
            {
                InflictVitaePenalty();
            }

            var msgPurgeEnchantments = new GameEventMagicPurgeEnchantments(Session);

            EnchantmentManager.RemoveAllEnchantments();
            Session.Network.EnqueueSend(msgPurgeEnchantments);

            // wait for the death animation to finish
            var dieChain   = new ActionChain();
            var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.Dead);

            dieChain.AddDelaySeconds(animLength + 1.0f);

            // enter portal space
            dieChain.AddAction(this, CreateCorpse);
            dieChain.AddAction(this, TeleportOnDeath);
            dieChain.AddAction(this, SetLifestoneProtection);
            dieChain.AddAction(this, SetMinimumTimeSincePK);

            dieChain.EnqueueChain();
        }
Пример #3
0
        public void HandleActionTeleToHouse()
        {
            if (PKTimerActive)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
                return;
            }

            if (RecallsDisabled)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
                return;
            }

            if (House == null)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand));
                return;
            }

            if (CombatMode != CombatMode.NonCombat)
            {
                // this should be handled by a different thing, probably a function that forces player into peacemode
                var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
                SetCombatMode(CombatMode.NonCombat);
                Session.Network.EnqueueSend(updateCombatMode);
            }

            EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling home.", ChatMessageType.Recall), 96.0f);
            EnqueueBroadcastMotion(motionHouseRecall);

            var startPos = new Position(Location);

            // Wait for animation
            var actionChain = new ActionChain();

            // Then do teleport
            var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.HouseRecall);

            actionChain.AddDelaySeconds(animLength);
            actionChain.AddAction(this, () =>
            {
                var endPos = new Position(Location);
                if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
                {
                    Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
                    return;
                }

                if (House != null)
                {
                    Teleport(House.SlumLord.Location);
                }
            });

            actionChain.EnqueueChain();
        }
Пример #4
0
        /// <summary>
        /// Handles teleporting a player to the lifestone (/ls or /lifestone command)
        /// </summary>
        public void HandleActionTeleToLifestone()
        {
            if (PKTimerActive)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
                return;
            }

            if (RecallsDisabled)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
                return;
            }

            if (Sanctuary == null)
            {
                Session.Network.EnqueueSend(new GameMessageSystemChat("Your spirit has not been attuned to a sanctuary location.", ChatMessageType.Broadcast));
                return;
            }

            // FIXME(ddevec): I should probably make a better interface for this
            UpdateVital(Mana, Mana.Current / 2);

            if (CombatMode != CombatMode.NonCombat)
            {
                // this should be handled by a different thing, probably a function that forces player into peacemode
                var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
                SetCombatMode(CombatMode.NonCombat);
                Session.Network.EnqueueSend(updateCombatMode);
            }

            EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling to the lifestone.", ChatMessageType.Recall), 96.0f);
            EnqueueBroadcastMotion(motionLifestoneRecall);

            var startPos = new Position(Location);

            // Wait for animation
            ActionChain lifestoneChain = new ActionChain();

            // Then do teleport
            lifestoneChain.AddDelaySeconds(DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.LifestoneRecall));
            lifestoneChain.AddAction(this, () =>
            {
                var endPos = new Position(Location);
                if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
                {
                    Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
                    return;
                }

                Teleport(Sanctuary);
            });

            lifestoneChain.EnqueueChain();
        }
Пример #5
0
        /// <summary>
        /// Broadcasts the player death animation, updates vitae, and sends network messages for player death
        /// Queues the action to call TeleportOnDeath and enter portal space soon
        /// </summary>
        protected override void Die(WorldObject lastDamager, WorldObject topDamager)
        {
            UpdateVital(Health, 0);
            NumDeaths++;
            DeathLevel  = Level; // for calculating vitae XP
            VitaeCpPool = 0;     // reset vitae XP earned

            // killer = top damager for looting rights
            if (topDamager != null)
            {
                Killer = topDamager.Guid.Full;
            }

            // broadcast death animation
            var deathAnim = new Motion(MotionStance.NonCombat, MotionCommand.Dead);

            EnqueueBroadcastMotion(deathAnim);

            // killer death message = last damager
            var killerMsg           = lastDamager != null ? " to " + lastDamager.Name : "";
            var currentDeathMessage = $"died{killerMsg}.";

            // create network messages for player death
            var msgHealthUpdate = new GameMessagePrivateUpdateAttribute2ndLevel(this, Vital.Health, 0);

            // TODO: death sounds? seems to play automatically in client
            // var msgDeathSound = new GameMessageSound(Guid, Sound.Death1, 1.0f);
            var msgNumDeaths         = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.NumDeaths, NumDeaths ?? 0);
            var msgDeathLevel        = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.DeathLevel, DeathLevel ?? 0);
            var msgVitaeCpPool       = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.VitaeCpPool, VitaeCpPool.Value);
            var msgPurgeEnchantments = new GameEventMagicPurgeEnchantments(Session);

            // update vitae
            var vitae = EnchantmentManager.UpdateVitae();

            var spellID             = (uint)SpellId.Vitae;
            var spell               = new Spell(spellID);
            var vitaeEnchantment    = new Enchantment(this, Guid, spellID, spell.Duration, 0, (EnchantmentMask)spell.StatModType, vitae);
            var msgVitaeEnchantment = new GameEventMagicUpdateEnchantment(Session, vitaeEnchantment);

            // send network messages for player death
            Session.Network.EnqueueSend(msgHealthUpdate, msgNumDeaths, msgDeathLevel, msgVitaeCpPool, msgPurgeEnchantments, msgVitaeEnchantment);

            // wait for the death animation to finish
            var dieChain   = new ActionChain();
            var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.Dead);

            dieChain.AddDelaySeconds(animLength + 1.0f);

            // enter portal space
            dieChain.AddAction(this, CreateCorpse);
            dieChain.AddAction(this, TeleportOnDeath);
            dieChain.EnqueueChain();
        }
Пример #6
0
        public void HandleActionRecallAllegianceHometown()
        {
            //Console.WriteLine($"{Name}.HandleActionRecallAllegianceHometown()");

            // check if player is in an allegiance
            if (Allegiance == null)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNotInAllegiance));
                return;
            }

            if (Allegiance.Sanctuary == null)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourAllegianceDoesNotHaveHometown));
                return;
            }

            if (CombatMode != CombatMode.NonCombat)
            {
                // this should be handled by a different thing, probably a function that forces player into peacemode
                var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
                SetCombatMode(CombatMode.NonCombat);
                Session.Network.EnqueueSend(updateCombatMode);
            }

            EnqueueBroadcast(new GameMessageSystemChat($"{Name} is going to the Allegiance hometown.", ChatMessageType.Recall), 96.0f);
            EnqueueBroadcastMotion(motionAllegianceHometownRecall);

            var startPos = new Position(Location);

            // Wait for animation
            var actionChain = new ActionChain();

            // Then do teleport
            var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.AllegianceHometownRecall);

            actionChain.AddDelaySeconds(animLength);
            actionChain.AddAction(this, () =>
            {
                var endPos = new Position(Location);
                if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
                {
                    Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
                    return;
                }

                Teleport(Allegiance.Sanctuary);
            });

            actionChain.EnqueueChain();
        }
Пример #7
0
        public void HandleActionTeleToMarketPlace()
        {
            if (PKTimerActive)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveBeenInPKBattleTooRecently));
                return;
            }

            if (RecallsDisabled)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
                return;
            }

            var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);

            EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling to the marketplace.", ChatMessageType.Recall), 96.0f);
            Session.Network.EnqueueSend(updateCombatMode); // this should be handled by a different thing, probably a function that forces player into peacemode
            EnqueueBroadcastMotion(motionMarketplaceRecall);

            var startPos = new Position(Location);

            // TODO: (OptimShi): Actual animation length is longer than in retail. 18.4s
            // float mpAnimationLength = MotionTable.GetAnimationLength((uint)MotionTableId, MotionCommand.MarketplaceRecall);
            // mpChain.AddDelaySeconds(mpAnimationLength);
            ActionChain mpChain = new ActionChain();

            mpChain.AddDelaySeconds(14);

            // Then do teleport
            mpChain.AddAction(this, () =>
            {
                var endPos = new Position(Location);
                if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
                {
                    Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
                    return;
                }

                Teleport(MarketplaceDrop);
            });

            // Set the chain to run
            mpChain.EnqueueChain();
        }
Пример #8
0
        /// <summary>
        /// Inflicts vitae
        /// </summary>
        public void InflictVitaePenalty(int amount = 5)
        {
            DeathLevel  = Level; // for calculating vitae XP
            VitaeCpPool = 0;     // reset vitae XP earned

            var msgDeathLevel  = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.DeathLevel, DeathLevel ?? 0);
            var msgVitaeCpPool = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.VitaeCpPool, VitaeCpPool.Value);

            Session.Network.EnqueueSend(msgDeathLevel, msgVitaeCpPool);

            var vitae = EnchantmentManager.UpdateVitae();

            var spellID          = (uint)SpellId.Vitae;
            var spell            = new Spell(spellID);
            var vitaeEnchantment = new Enchantment(this, Guid.Full, spellID, 0, (EnchantmentMask)spell.StatModType, vitae);

            Session.Network.EnqueueSend(new GameEventMagicUpdateEnchantment(Session, vitaeEnchantment));
        }
        public static void Handle(ClientMessage clientMessage, Session session)
        {
            string message = $"{session.Player.Name} is recalling to the marketplace.";

            var sysChatMessage = new GameMessageSystemChat(message, ChatMessageType.Recall);

            var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(session, PropertyInt.CombatMode, 1);

            var motionMarketplaceRecall = new UniversalMotion(MotionStance.Standing, new MotionItem(MotionCommand.MarketplaceRecall));

            var animationEvent = new GameMessageUpdateMotion(session.Player, session, motionMarketplaceRecall);

            // TODO: This needs to be changed to broadcast sysChatMessage to only those in local chat hearing range
            // FIX: Recall text isn't being broadcast yet, need to address
            session.Network.EnqueueSend(updateCombatMode, sysChatMessage);
            session.Player.EnqueueMovementEvent(motionMarketplaceRecall, session.Player.Guid);

            session.Player.SetDelayedTeleport(TimeSpan.FromSeconds(14), marketplaceDrop);
        }
Пример #10
0
        /// <summary>
        /// Handles the GameAction 0x47 - TrainSkill network message from client
        /// </summary>
        public bool HandleActionTrainSkill(Skill skill, int creditsSpent)
        {
            if (creditsSpent > AvailableSkillCredits)
            {
                log.Error($"{Name}.HandleActionTrainSkill({skill}, {creditsSpent}) - not enough skill credits ({AvailableSkillCredits})");
                return(false);
            }

            // get the actual cost to train the skill.
            if (!DatManager.PortalDat.SkillTable.SkillBaseHash.TryGetValue((uint)skill, out var skillBase))
            {
                log.Error($"{Name}.HandleActionTrainSkill({skill}, {creditsSpent}) - couldn't find skill base");
                return(false);
            }

            if (creditsSpent != skillBase.TrainedCost)
            {
                log.Error($"{Name}.HandleActionTrainSkill({skill}, {creditsSpent}) - client value differs from skillBase.TrainedCost({skillBase.TrainedCost})");
                return(false);
            }

            // attempt to train the specified skill
            var success = TrainSkill(skill, creditsSpent);

            var availableSkillCredits = $"You now have {AvailableSkillCredits} credits available.";

            if (success)
            {
                var updateSkill  = new GameMessagePrivateUpdateSkill(this, GetCreatureSkill(skill));
                var skillCredits = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.AvailableSkillCredits, AvailableSkillCredits ?? 0);

                var msg = new GameMessageSystemChat($"{skill.ToSentence()} trained. {availableSkillCredits}", ChatMessageType.Advancement);

                Session.Network.EnqueueSend(updateSkill, skillCredits, msg);
            }
            else
            {
                Session.Network.EnqueueSend(new GameMessageSystemChat($"Failed to train {skill.ToSentence()}! {availableSkillCredits}", ChatMessageType.Advancement));
            }

            return(success);
        }
        private static readonly Position marketplaceDrop = new Position(23855548, 49.16f, -31.62f, 0.10f, 0f, 0f, -0.71f, 0.71f); // Is this the right drop?

        public override void Handle()
        {
            string message = $"{Session.Player.Name} is recalling to the marketplace.";

            var sysChatMessage = new GameMessageSystemChat(message, ChatMessageType.Recall);

            // TODO: This is missing the floaty animation wind up and appropriate pause before teleportation begins.

            // This is the pcap verified message sent to change just the current mana
            // Not needed in this command, leaving for example
            // var updatePlayersMana = new GameMessagePrivateUpdateAttribute2ndLevel(Session, Vital.Mana, Session.Player.Mana.Current / 2);

            var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(Session, PropertyInt.CombatMode, 1);

            // TODO: This needs to be changed to broadcast sysChatMessage to only those in local chat hearing range
            Session.Network.EnqueueSend(updateCombatMode, sysChatMessage);

            // TODO: Wait until MovementEvent completes then send the following message
            Session.Player.Teleport(marketplaceDrop);
        }
Пример #12
0
        // FIXME(ddevec): I broke vendors -- I need to think about how to reimplement this cleanly
        public override void OnUse(ObjectGuid playerId)
        {
            ActionChain chain = new ActionChain();

            CurrentLandblock.ChainOnObject(chain, playerId, (WorldObject wo) =>
            {
                Player player = wo as Player;
                if (player == null)
                {
                    return;
                }

                string serverMessage = null;
                // validate within use range, taking into account the radius of the model itself, as well
                SetupModel csetup   = SetupModel.ReadFromDat(PhysicsData.CSetup);
                float radiusSquared = (GameData.UseRadius + csetup.Radius) * (GameData.UseRadius + csetup.Radius);

                // We're static, so this is safe -- our Location is never written
                if (player.Location.SquaredDistanceTo(Location) >= radiusSquared)
                {
                    serverMessage = "Your way to far away to trust, come closer and buy something or leave me alone!";
                }
                else
                {
                    // create the outbound server message
                    serverMessage = "You Break it, you bought it!";
                }

                // give player starting money
                var money = new GameMessagePrivateUpdatePropertyInt(player.Session, PropertyInt.CoinValue, 5000);
                player.Session.Network.EnqueueSend(money);

                var welcomemsg = new GameMessageSystemChat(serverMessage, ChatMessageType.Tell);
                // always send useDone event
                var sendUseDoneEvent = new GameEventUseDone(player.Session);
                player.Session.Network.EnqueueSend(welcomemsg, sendUseDoneEvent);

                player.Session.Network.EnqueueSend(new GameEventApproachVendor(player.Session, Guid));
            });
            chain.EnqueueChain();
        }
Пример #13
0
        public void HandleActionTeleToMarketPlace()
        {
            var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);

            EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling to the marketplace.", ChatMessageType.Recall));
            Session.Network.EnqueueSend(updateCombatMode); // this should be handled by a different thing, probably a function that forces player into peacemode
            EnqueueBroadcastMotion(motionMarketplaceRecall);

            // TODO: (OptimShi): Actual animation length is longer than in retail. 18.4s
            // float mpAnimationLength = MotionTable.GetAnimationLength((uint)MotionTableId, MotionCommand.MarketplaceRecall);
            // mpChain.AddDelaySeconds(mpAnimationLength);
            ActionChain mpChain = new ActionChain();

            mpChain.AddDelaySeconds(14);

            // Then do teleport
            mpChain.AddAction(this, () => Teleport(MarketplaceDrop));

            // Set the chain to run
            mpChain.EnqueueChain();
        }
Пример #14
0
        /// <summary>
        /// Player Death/Kill, use this to kill a session's player
        /// </summary>
        /// <remarks>
        ///     TODO:
        ///         1. Find the best vitae formula and add vitae
        ///         2. Generate the correct death message, or have it passed in as a parameter.
        ///         3. Find the correct player death noise based on the player model and play on death.
        ///         4. Determine if we need to Send Queued Action for Lifestone Materialize, after Action Location.
        ///         5. Find the health after death formula and Set the correct health
        /// </remarks>
        private void OnKill(Session killerSession)
        {
            ObjectGuid killerId = killerSession.Player.Guid;

            IsAlive        = false;
            Health.Current = 0;  // Set the health to zero
            NumDeaths++;         // Increase the NumDeaths counter
            DeathLevel  = Level; // For calculating vitae XP
            VitaeCpPool = 0;     // Set vitae XP

            // TODO: Generate a death message based on the damage type to pass in to each death message:
            string currentDeathMessage = $"died to {killerSession.Player.Name}.";

            // Send Vicitim Notification, or "Your Death" event to the client:
            // create and send the client death event, GameEventYourDeath
            var msgYourDeath         = new GameEventYourDeath(Session, $"You have {currentDeathMessage}");
            var msgNumDeaths         = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.NumDeaths, NumDeaths ?? 0);
            var msgDeathLevel        = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.DeathLevel, DeathLevel ?? 0);
            var msgVitaeCpPool       = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.VitaeCpPool, VitaeCpPool.Value);
            var msgPurgeEnchantments = new GameEventPurgeAllEnchantments(Session);
            // var msgDeathSound = new GameMessageSound(Guid, Sound.Death1, 1.0f);

            // handle vitae
            var vitae = EnchantmentManager.UpdateVitae();

            var spellID             = (uint)Network.Enum.Spell.Vitae;
            var spellBase           = DatManager.PortalDat.SpellTable.Spells[spellID];
            var spell               = DatabaseManager.World.GetCachedSpell(spellID);
            var vitaeEnchantment    = new Enchantment(this, spellID, (double)spell.Duration, 0, spell.StatModType, vitae);
            var msgVitaeEnchantment = new GameEventMagicUpdateEnchantment(Session, vitaeEnchantment);

            var msgHealthUpdate = new GameMessagePrivateUpdateAttribute2ndLevel(this, Vital.Health, 0);

            // Send first death message group
            Session.Network.EnqueueSend(msgHealthUpdate, msgYourDeath, msgNumDeaths, msgDeathLevel, msgVitaeCpPool, msgPurgeEnchantments, msgVitaeEnchantment);

            // Broadcast the 019E: Player Killed GameMessage
            ActionBroadcastKill($"{Name} has {currentDeathMessage}", Guid, killerId);
        }
Пример #15
0
        /// <summary>
        /// Resets the skill, refunds all experience and skill credits, if allowed.
        /// </summary>
        public bool ResetSkill(Skill skill)
        {
            var creatureSkill = GetCreatureSkill(skill, false);

            if (creatureSkill == null || creatureSkill.AdvancementClass < SkillAdvancementClass.Trained)
            {
                return(false);
            }

            // gather skill credits to refund
            DatManager.PortalDat.SkillTable.SkillBaseHash.TryGetValue((uint)creatureSkill.Skill, out var skillBase);

            if (skillBase == null)
            {
                return(false);
            }

            // salvage / tinkering skills specialized via augmentations
            // cannot be untrained or unspecialized
            bool specAug = false;

            switch (creatureSkill.Skill)
            {
            case Skill.ArmorTinkering:
                specAug = AugmentationSpecializeArmorTinkering > 0;
                break;

            case Skill.ItemTinkering:
                specAug = AugmentationSpecializeItemTinkering > 0;
                break;

            case Skill.MagicItemTinkering:
                specAug = AugmentationSpecializeMagicItemTinkering > 0;
                break;

            case Skill.WeaponTinkering:
                specAug = AugmentationSpecializeWeaponTinkering > 0;
                break;

            case Skill.Salvaging:
                specAug = AugmentationSpecializeSalvaging > 0;
                break;
            }

            //if (specAug)
            //    return false;   // send message?

            var typeOfSkill  = creatureSkill.AdvancementClass.ToString().ToLower() + " ";
            var untrainable  = IsSkillUntrainable(skill);
            var creditRefund = creatureSkill.AdvancementClass == SkillAdvancementClass.Specialized || untrainable;

            if (creatureSkill.AdvancementClass == SkillAdvancementClass.Specialized && !specAug)
            {
                creatureSkill.AdvancementClass = SkillAdvancementClass.Trained;
                creatureSkill.InitLevel        = 0;
                AvailableSkillCredits         += skillBase.UpgradeCostFromTrainedToSpecialized;
            }

            // temple untraining 'always trained' skills:
            // cannot be untrained, but skill XP can be recovered
            if (untrainable && !specAug)
            {
                creatureSkill.AdvancementClass = SkillAdvancementClass.Untrained;
                creatureSkill.InitLevel        = 0;
                AvailableSkillCredits         += skillBase.TrainedCost;
            }

            RefundXP(creatureSkill.ExperienceSpent);

            creatureSkill.ExperienceSpent = 0;
            creatureSkill.Ranks           = 0;

            var updateSkill           = new GameMessagePrivateUpdateSkill(this, creatureSkill);
            var availableSkillCredits = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.AvailableSkillCredits, AvailableSkillCredits ?? 0);

            var msg = $"Your {(untrainable && !specAug ? $"{typeOfSkill}" : "")}{skill.ToSentence()} skill has been {(untrainable && !specAug ? "removed" : "reset")}. ";

            msg += $"All the experience {(creditRefund && !specAug ? "and skill credits " : "")}that you spent on this skill have been refunded to you.";

            Session.Network.EnqueueSend(updateSkill, availableSkillCredits, new GameMessageSystemChat(msg, ChatMessageType.Broadcast));

            return(true);
        }
Пример #16
0
        /// <summary>
        /// Broadcasts the player death animation, updates vitae, and sends network messages for player death
        /// Queues the action to call TeleportOnDeath and enter portal space soon
        /// </summary>
        protected override void Die(WorldObject lastDamager, WorldObject topDamager)
        {
            UpdateVital(Health, 0);
            NumDeaths++;
            suicideInProgress = false;

            // killer = top damager for looting rights
            if (topDamager != null)
            {
                KillerId = topDamager.Guid.Full;
            }

            // broadcast death animation
            var deathAnim = new Motion(MotionStance.NonCombat, MotionCommand.Dead);

            EnqueueBroadcastMotion(deathAnim);

            // create network messages for player death
            var msgHealthUpdate = new GameMessagePrivateUpdateAttribute2ndLevel(this, Vital.Health, 0);

            // TODO: death sounds? seems to play automatically in client
            // var msgDeathSound = new GameMessageSound(Guid, Sound.Death1, 1.0f);
            var msgNumDeaths = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.NumDeaths, NumDeaths);

            // send network messages for player death
            Session.Network.EnqueueSend(msgHealthUpdate, msgNumDeaths);

            if (lastDamager.Guid == Guid) // suicide
            {
                var msgSelfInflictedDeath = new GameEventWeenieError(Session, WeenieError.YouKilledYourself);
                Session.Network.EnqueueSend(msgSelfInflictedDeath);
            }

            // update vitae
            // players who died in a PKLite fight do not accrue vitae
            if (!IsPKLiteDeath(topDamager))
            {
                InflictVitaePenalty();
            }

            if (IsPKDeath(topDamager) || AugmentationSpellsRemainPastDeath == 0)
            {
                var msgPurgeEnchantments = new GameEventMagicPurgeEnchantments(Session);
                EnchantmentManager.RemoveAllEnchantments();
                Session.Network.EnqueueSend(msgPurgeEnchantments);
            }

            // wait for the death animation to finish
            var dieChain   = new ActionChain();
            var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.Dead);

            dieChain.AddDelaySeconds(animLength + 1.0f);

            dieChain.AddAction(this, () =>
            {
                CreateCorpse(topDamager);
                TeleportOnDeath();      // enter portal space
                SetLifestoneProtection();

                if (IsPKDeath(topDamager) || IsPKLiteDeath(topDamager))
                {
                    SetMinimumTimeSincePK();
                }
            });

            dieChain.EnqueueChain();
        }
Пример #17
0
        /// <summary>
        /// Determines if the player has advanced a level
        /// </summary>
        /// <remarks>
        /// Known issues:
        ///         1. XP updates from outside of the grantxp command have not been done yet.
        /// </remarks>
        private void CheckForLevelup()
        {
            // Question: Where do *we* call CheckForLevelup()? :
            //      From within the player.cs file, the options might be:
            //           GrantXp()
            //      From outside of the player.cs file, we may call CheckForLevelup() durring? :
            //           XP Updates?
            var xpTable = DatManager.PortalDat.XpTable;

            var  startingLevel = Level;
            var  maxLevel      = xpTable.CharacterLevelXPList.Count - 1;
            bool creditEarned  = false;

            if (Level == maxLevel)
            {
                return;
            }

            // increases until the correct level is found
            while (xpTable.CharacterLevelXPList[Level ?? 1 + 1] <= (ulong)TotalExperience)
            {
                Level++;

                // increase the skill credits if the chart allows this level to grant a credit
                if (xpTable.CharacterLevelSkillCreditList[Level ?? 1] > 0)
                {
                    AvailableSkillCredits += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 1];
                    TotalSkillCredits     += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 1];
                    creditEarned           = true;
                }

                // break if we reach max
                if (Level == maxLevel)
                {
                    PlayParticleEffect(ACE.Entity.Enum.PlayScript.WeddingBliss, Guid);
                    break;
                }
            }

            if (Level > startingLevel)
            {
                string levelUpMessageText = (Level == maxLevel) ? $"You have reached the maximum level of {Level}!" : $"You are now level {Level}!";
                var    levelUpMessage     = new GameMessageSystemChat(levelUpMessageText, ChatMessageType.Advancement);

                string xpUpdateText    = (AvailableSkillCredits > 0) ? $"You have {AvailableExperience:#,###0} experience points and {AvailableSkillCredits} skill credits available to raise skills and attributes." : $"You have {AvailableExperience:#,###0} experience points available to raise skills and attributes.";
                var    xpUpdateMessage = new GameMessageSystemChat(xpUpdateText, ChatMessageType.Advancement);

                var levelUp        = new GameMessagePrivateUpdatePropertyInt(Session.Player.Sequences, PropertyInt.Level, Level ?? 1);
                var currentCredits = new GameMessagePrivateUpdatePropertyInt(Session.Player.Sequences, PropertyInt.AvailableSkillCredits, AvailableSkillCredits ?? 0);

                if (Level != maxLevel && !creditEarned)
                {
                    var nextLevelWithCredits = 0;

                    for (int i = (Level ?? 1) + 1; i <= maxLevel; i++)
                    {
                        if (xpTable.CharacterLevelSkillCreditList[i] > 0)
                        {
                            nextLevelWithCredits = i;
                            break;
                        }
                    }

                    string nextCreditAtText  = $"You will earn another skill credit at {nextLevelWithCredits}";
                    var    nextCreditMessage = new GameMessageSystemChat(nextCreditAtText, ChatMessageType.Advancement);
                    Session.Network.EnqueueSend(levelUp, levelUpMessage, xpUpdateMessage, currentCredits, nextCreditMessage);
                }
                else
                {
                    Session.Network.EnqueueSend(levelUp, levelUpMessage, xpUpdateMessage, currentCredits);
                }

                // play level up effect
                PlayParticleEffect(ACE.Entity.Enum.PlayScript.LevelUp, Guid);
            }
        }
Пример #18
0
        /// <summary>
        /// Determines if the player has advanced a level
        /// </summary>
        private void CheckForLevelup()
        {
            var xpTable = DatManager.PortalDat.XpTable;

            var maxLevel = GetMaxLevel();

            if (Level >= maxLevel)
            {
                return;
            }

            var  startingLevel = Level;
            bool creditEarned  = false;

            // increases until the correct level is found
            while ((ulong)(TotalExperience ?? 0) >= xpTable.CharacterLevelXPList[(Level ?? 0) + 1])
            {
                Level++;

                // increase the skill credits if the chart allows this level to grant a credit
                if (xpTable.CharacterLevelSkillCreditList[Level ?? 0] > 0)
                {
                    AvailableSkillCredits += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 0];
                    TotalSkillCredits     += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 0];
                    creditEarned           = true;
                }

                // break if we reach max
                if (Level == maxLevel)
                {
                    PlayParticleEffect(PlayScript.WeddingBliss, Guid);
                    break;
                }
            }

            if (Level > startingLevel)
            {
                var message = (Level == maxLevel) ? $"You have reached the maximum level of {Level}!" : $"You are now level {Level}!";

                message += (AvailableSkillCredits > 0) ? $"\nYou have {AvailableExperience:#,###0} experience points and {AvailableSkillCredits} skill credits available to raise skills and attributes." : $"\nYou have {AvailableExperience:#,###0} experience points available to raise skills and attributes.";

                var levelUp        = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.Level, Level ?? 1);
                var currentCredits = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.AvailableSkillCredits, AvailableSkillCredits ?? 0);

                if (Level != maxLevel && !creditEarned)
                {
                    var nextLevelWithCredits = 0;

                    for (int i = (Level ?? 0) + 1; i <= maxLevel; i++)
                    {
                        if (xpTable.CharacterLevelSkillCreditList[i] > 0)
                        {
                            nextLevelWithCredits = i;
                            break;
                        }
                    }
                    message += $"\nYou will earn another skill credit at level {nextLevelWithCredits}.";
                }

                if (Fellowship != null)
                {
                    Fellowship.OnFellowLevelUp(this);
                }

                if (AllegianceNode != null)
                {
                    AllegianceNode.OnLevelUp();
                }

                Session.Network.EnqueueSend(levelUp);

                SetMaxVitals();

                // play level up effect
                PlayParticleEffect(PlayScript.LevelUp, Guid);

                Session.Network.EnqueueSend(new GameMessageSystemChat(message, ChatMessageType.Advancement), currentCredits);
            }
        }
Пример #19
0
        /// <summary>
        /// Broadcasts the player death animation, updates vitae, and sends network messages for player death
        /// Queues the action to call TeleportOnDeath and enter portal space soon
        /// </summary>
        protected override void Die(DamageHistoryInfo lastDamager, DamageHistoryInfo topDamager)
        {
            if (topDamager?.Guid == Guid && IsPKType)
            {
                var topDamagerOther = DamageHistory.GetTopDamager(false);

                if (topDamagerOther != null && topDamagerOther.IsPlayer)
                {
                    topDamager = topDamagerOther;
                }
            }

            UpdateVital(Health, 0);
            NumDeaths++;
            suicideInProgress = false;

            if (CombatMode == CombatMode.Magic && MagicState.IsCasting)
            {
                FailCast(false);
            }

            // TODO: instead of setting IsBusy here,
            // eventually all of the places that check for states such as IsBusy || Teleporting
            // might want to use a common function, and IsDead should return a separate error
            IsBusy = true;

            // killer = top damager for looting rights
            if (topDamager != null)
            {
                KillerId = topDamager.Guid.Full;
            }

            // broadcast death animation
            var deathAnim = new Motion(MotionStance.NonCombat, MotionCommand.Dead);

            EnqueueBroadcastMotion(deathAnim);

            // create network messages for player death
            var msgHealthUpdate = new GameMessagePrivateUpdateAttribute2ndLevel(this, Vital.Health, 0);

            // TODO: death sounds? seems to play automatically in client
            // var msgDeathSound = new GameMessageSound(Guid, Sound.Death1, 1.0f);
            var msgNumDeaths = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.NumDeaths, NumDeaths);

            // send network messages for player death
            Session.Network.EnqueueSend(msgHealthUpdate, msgNumDeaths);

            if (lastDamager?.Guid == Guid) // suicide
            {
                var msgSelfInflictedDeath = new GameEventWeenieError(Session, WeenieError.YouKilledYourself);
                Session.Network.EnqueueSend(msgSelfInflictedDeath);
            }

            // update vitae
            // players who died in a PKLite fight do not accrue vitae
            if (!IsPKLiteDeath(topDamager))
            {
                InflictVitaePenalty();
            }

            if (IsPKDeath(topDamager) || AugmentationSpellsRemainPastDeath == 0)
            {
                var msgPurgeEnchantments = new GameEventMagicPurgeEnchantments(Session);
                EnchantmentManager.RemoveAllEnchantments();
                Session.Network.EnqueueSend(msgPurgeEnchantments);
            }

            // wait for the death animation to finish
            var dieChain   = new ActionChain();
            var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.Dead);

            dieChain.AddDelaySeconds(animLength + 1.0f);

            dieChain.AddAction(this, () =>
            {
                CreateCorpse(topDamager);

                ThreadSafeTeleportOnDeath(); // enter portal space

                if (IsPKDeath(topDamager) || IsPKLiteDeath(topDamager))
                {
                    SetMinimumTimeSincePK();
                }

                IsBusy = false;
            });

            dieChain.EnqueueChain();
        }
Пример #20
0
        /// <summary>
        /// Determines if the player has advanced a level
        /// </summary>
        private void CheckForLevelup()
        {
            var xpTable = DatManager.PortalDat.XpTable;

            var maxLevel = GetMaxLevel();

            if (Level >= maxLevel)
            {
                return;
            }

            var  startingLevel = Level;
            bool creditEarned  = false;

            // if level is not under 275(false) do normal stuff, otherwise if over 274(true) do custom function.

            // increases until the correct level is found
            while ((ulong)(TotalExperience ?? 0) >= ((Level < 275) ? xpTable.CharacterLevelXPList[(Level ?? 0) + 1] : (ulong)Catch275(false, startingLevel)))
            {
                Level++;

                // increase the skill credits if the chart allows this level to grant a credit

                // gotta limit this to levels 275 and under otherwise causes crash.
                if (Level <= 275)
                {
                    if (xpTable.CharacterLevelSkillCreditList[Level ?? 0] > 0)
                    {
                        AvailableSkillCredits += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 0];
                        TotalSkillCredits     += (int)xpTable.CharacterLevelSkillCreditList[Level ?? 0];
                        creditEarned           = true;
                    }
                }

                // if a player is level 300 or higher and level is also divisible by 30 evenly give a skill credit.
                if (Level > 275)
                {
                    if (Level >= 300 && Level % 30 == 0)
                    {
                        AvailableSkillCredits += 1;
                        TotalSkillCredits     += 1;
                        creditEarned           = true;
                    }
                }

                // break if we reach max
                if (Level == maxLevel)
                {
                    PlayParticleEffect(PlayScript.WeddingBliss, Guid);
                    break;
                }
            }

            if (Level > startingLevel)
            {
                var message = (Level == maxLevel) ? $"You have reached the maximum level of {Level}!" : $"You are now level {Level}!";

                message += (AvailableSkillCredits > 0) ? $"\nYou have {AvailableExperience:#,###0} experience points and {AvailableSkillCredits} skill credits available to raise skills and attributes." : $"\nYou have {AvailableExperience:#,###0} experience points available to raise skills and attributes.";

                var levelUp        = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.Level, Level ?? 1);
                var currentCredits = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.AvailableSkillCredits, AvailableSkillCredits ?? 0);

                // 275 and under code.
                if (Level != maxLevel && !creditEarned && Level <= 275)
                {
                    var nextLevelWithCredits = 0;

                    for (int i = (Level ?? 0) + 1; i <= maxLevel; i++)
                    {
                        if (xpTable.CharacterLevelSkillCreditList[i] > 0)
                        {
                            nextLevelWithCredits = i;
                            break;
                        }
                    }
                    message += $"\nYou will earn another skill credit at level {nextLevelWithCredits}.";
                }

                if (Fellowship != null)
                {
                    Fellowship.OnFellowLevelUp(this);
                }

                if (AllegianceNode != null)
                {
                    AllegianceNode.OnLevelUp();
                }

                Session.Network.EnqueueSend(levelUp);

                SetMaxVitals();

                // play level up effect
                PlayParticleEffect(PlayScript.LevelUp, Guid);

                Session.Network.EnqueueSend(new GameMessageSystemChat(message, ChatMessageType.Advancement), currentCredits);
            }
        }
Пример #21
0
        public static void HandleRecallDuels(Session session, params string[] parameters)
        {
            if (RealmManager.DuelRealm == null)
            {
                return;
            }
            var player = session.Player;

            if (!player.ValidatePlayerRealmPosition(DuelRealmHelpers.GetDuelingAreaDrop()))
            {
                return;
            }

            if (player.PKTimerActive)
            {
                session.Network.EnqueueSend(new GameEventWeenieError(session, WeenieError.YouHaveBeenInPKBattleTooRecently));
                return;
            }

            if (player.RecallsDisabled)
            {
                session.Network.EnqueueSend(new GameEventWeenieError(session, WeenieError.ExitTrainingAcademyToUseCommand));
                return;
            }

            if (player.TooBusyToRecall)
            {
                session.Network.EnqueueSend(new GameEventWeenieError(session, WeenieError.YoureTooBusy));
                return;
            }

            if (player.CombatMode != CombatMode.NonCombat)
            {
                // this should be handled by a different thing, probably a function that forces player into peacemode
                var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(player, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
                player.SetCombatMode(CombatMode.NonCombat);
                session.Network.EnqueueSend(updateCombatMode);
            }

            player.EnqueueBroadcast(new GameMessageSystemChat($"{player.Name} is recalling to the duel staging area.", ChatMessageType.Recall), Player.LocalBroadcastRange, ChatMessageType.Recall);

            player.SendMotionAsCommands(MotionCommand.MarketplaceRecall, MotionStance.NonCombat);

            var startPos = new Position(player.Location);

            // TODO: (OptimShi): Actual animation length is longer than in retail. 18.4s
            // float mpAnimationLength = MotionTable.GetAnimationLength((uint)MotionTableId, MotionCommand.MarketplaceRecall);
            // mpChain.AddDelaySeconds(mpAnimationLength);
            ActionChain mpChain = new ActionChain();

            mpChain.AddDelaySeconds(5);

            // Then do teleport
            player.IsBusy = true;
            mpChain.AddAction(player, () =>
            {
                player.IsBusy = false;
                var endPos    = new Position(player.Location);
                if (startPos.SquaredDistanceTo(endPos) > Player.RecallMoveThresholdSq)
                {
                    session.Network.EnqueueSend(new GameEventWeenieError(session, WeenieError.YouHaveMovedTooFar));
                    return;
                }

                player.Teleport(DuelRealmHelpers.GetDuelingAreaDrop());
            });

            // Set the chain to run
            mpChain.EnqueueChain();
        }
Пример #22
0
        /// <summary>
        /// Determines if the player has advanced a level
        /// </summary>
        /// <remarks>
        /// Known issues:
        ///         1. XP updates from outside of the grantxp command have not been done yet.
        /// </remarks>
        private void CheckForLevelup()
        {
            // Question: Where do *we* call CheckForLevelup()? :
            //      From within the player.cs file, the options might be:
            //           GrantXp()
            //      From outside of the player.cs file, we may call CheckForLevelup() durring? :
            //           XP Updates?
            var            startingLevel = character.Level;
            var            chart         = DatabaseManager.Charts.GetLevelingXpChart();
            CharacterLevel maxLevel      = chart.Levels.Last();
            bool           creditEarned  = false;

            if (character.Level == maxLevel.Level)
            {
                return;
            }

            // increases until the correct level is found
            while (chart.Levels[Convert.ToInt32(character.Level)].TotalXp <= character.TotalExperience)
            {
                character.Level++;
                CharacterLevel newLevel = chart.Levels.FirstOrDefault(item => item.Level == character.Level);
                // increase the skill credits if the chart allows this level to grant a credit
                if (newLevel.GrantsSkillPoint)
                {
                    character.AvailableSkillCredits++;
                    character.TotalSkillCredits++;
                    creditEarned = true;
                }
                // break if we reach max
                if (character.Level == maxLevel.Level)
                {
                    PlayParticleEffect(Effect.WeddingBliss);
                    break;
                }
            }

            if (character.Level > startingLevel)
            {
                string level              = $"{character.Level}";
                string skillCredits       = $"{character.AvailableSkillCredits}";
                string xpAvailable        = $"{character.AvailableExperience:#,###0}";
                var    levelUp            = new GameMessagePrivateUpdatePropertyInt(Session, PropertyInt.Level, character.Level);
                string levelUpMessageText = (character.Level == maxLevel.Level) ? $"You have reached the maximum level of {level}!" : $"You are now level {level}!";
                var    levelUpMessage     = new GameMessageSystemChat(levelUpMessageText, ChatMessageType.Advancement);
                string xpUpdateText       = (character.AvailableSkillCredits > 0) ? $"You have {xpAvailable} experience points and {skillCredits} skill credits available to raise skills and attributes." : $"You have {xpAvailable} experience points available to raise skills and attributes.";
                var    xpUpdateMessage    = new GameMessageSystemChat(xpUpdateText, ChatMessageType.Advancement);
                var    currentCredits     = new GameMessagePrivateUpdatePropertyInt(Session, PropertyInt.AvailableSkillCredits, character.AvailableSkillCredits);
                if (character.Level != maxLevel.Level && !creditEarned)
                {
                    string nextCreditAtText  = $"You will earn another skill credit at {chart.Levels.Where(item => item.Level > character.Level).OrderBy(item => item.Level).First(item => item.GrantsSkillPoint).Level}";
                    var    nextCreditMessage = new GameMessageSystemChat(nextCreditAtText, ChatMessageType.Advancement);
                    Session.Network.EnqueueSend(levelUp, levelUpMessage, xpUpdateMessage, currentCredits, nextCreditMessage);
                }
                else
                {
                    Session.Network.EnqueueSend(levelUp, levelUpMessage, xpUpdateMessage, currentCredits);
                }
                // play level up effect
                PlayParticleEffect(Effect.LevelUp);
            }
        }
Пример #23
0
        /// <summary>
        /// Resets the skill, refunds all experience and skill credits, if allowed.
        /// </summary>
        public bool ResetSkill(Skill skill, bool refund = true)
        {
            var creatureSkill = GetCreatureSkill(skill, false);

            if (creatureSkill == null || creatureSkill.AdvancementClass < SkillAdvancementClass.Trained)
            {
                return(false);
            }

            // gather skill credits to refund
            DatManager.PortalDat.SkillTable.SkillBaseHash.TryGetValue((uint)creatureSkill.Skill, out var skillBase);

            if (skillBase == null)
            {
                return(false);
            }

            // salvage / tinkering skills specialized via augmentations
            // Salvaging cannot be untrained or unspecialized => skillIsSpecializedViaAugmentation && !untrainable
            IsSkillSpecializedViaAugmentation(creatureSkill.Skill, out var skillIsSpecializedViaAugmentation);

            var typeOfSkill  = creatureSkill.AdvancementClass.ToString().ToLower() + " ";
            var untrainable  = IsSkillUntrainable(skill);
            var creditRefund = (creatureSkill.AdvancementClass == SkillAdvancementClass.Specialized && !(skillIsSpecializedViaAugmentation && !untrainable)) || untrainable;

            if (creatureSkill.AdvancementClass == SkillAdvancementClass.Specialized && !(skillIsSpecializedViaAugmentation && !untrainable))
            {
                creatureSkill.AdvancementClass = SkillAdvancementClass.Trained;
                creatureSkill.InitLevel        = 0;
                if (!skillIsSpecializedViaAugmentation) // Tinkering skills can be unspecialized, but do not refund upgrade cost.
                {
                    AvailableSkillCredits += skillBase.UpgradeCostFromTrainedToSpecialized;
                }
            }

            // temple untraining 'always trained' skills:
            // cannot be untrained, but skill XP can be recovered
            if (untrainable)
            {
                creatureSkill.AdvancementClass = SkillAdvancementClass.Untrained;
                creatureSkill.InitLevel        = 0;
                AvailableSkillCredits         += skillBase.TrainedCost;
            }

            if (refund)
            {
                RefundXP(creatureSkill.ExperienceSpent);
            }

            creatureSkill.ExperienceSpent = 0;
            creatureSkill.Ranks           = 0;

            var updateSkill           = new GameMessagePrivateUpdateSkill(this, creatureSkill);
            var availableSkillCredits = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.AvailableSkillCredits, AvailableSkillCredits ?? 0);

            var msg = $"Your {(untrainable ? $"{typeOfSkill}" : "")}{skill.ToSentence()} skill has been {(untrainable ? "removed" : "reset")}. ";

            msg += $"All the experience {(creditRefund ? "and skill credits " : "")}that you spent on this skill have been refunded to you.";

            if (refund)
            {
                Session.Network.EnqueueSend(updateSkill, availableSkillCredits, new GameMessageSystemChat(msg, ChatMessageType.Broadcast));
            }
            else
            {
                Session.Network.EnqueueSend(updateSkill, new GameMessageSystemChat(msg, ChatMessageType.Broadcast));
            }

            return(true);
        }
Пример #24
0
        private void HandleGameAction(QueuedGameAction action, Player player)
        {
            switch (action.ActionType)
            {
            case GameActionType.TalkDirect:
            {
                // TODO: remove this hack (using TalkDirect) ASAP
                var         g   = new ObjectGuid(action.ObjectId);
                WorldObject obj = (WorldObject)player;
                if (worldObjects.ContainsKey(g))
                {
                    obj = worldObjects[g];
                }
                DeathMessageArgs d = new DeathMessageArgs(action.ActionBroadcastMessage, new ObjectGuid(action.ObjectId), new ObjectGuid(action.SecondaryObjectId));
                HandleDeathMessage(obj, d);
                break;
            }

            case GameActionType.TeleToHouse:
            case GameActionType.TeleToLifestone:
            case GameActionType.TeleToMansion:
            case GameActionType.TeleToMarketPlace:
            case GameActionType.TeleToPkArena:
            case GameActionType.TeleToPklArena:
            {
                player.Teleport(action.ActionLocation);
                break;
            }

            case GameActionType.ApplyVisualEffect:
            {
                var         g   = new ObjectGuid(action.ObjectId);
                WorldObject obj = (WorldObject)player;
                if (worldObjects.ContainsKey(g))
                {
                    obj = worldObjects[g];
                }
                var particleEffect = (PlayScript)action.SecondaryObjectId;
                HandleParticleEffectEvent(obj, particleEffect);
                break;
            }

            case GameActionType.ApplySoundEffect:
            {
                var         g   = new ObjectGuid(action.ObjectId);
                WorldObject obj = (WorldObject)player;
                if (worldObjects.ContainsKey(g))
                {
                    obj = worldObjects[g];
                }
                var soundEffect = (Sound)action.SecondaryObjectId;
                HandleSoundEvent(obj, soundEffect);
                break;
            }

            case GameActionType.IdentifyObject:
            {
                // TODO: Throttle this request. The live servers did this, likely for a very good reason, so we should, too.
                var         g   = new ObjectGuid(action.ObjectId);
                WorldObject obj = (WorldObject)player;
                if (worldObjects.ContainsKey(g))
                {
                    obj = worldObjects[g];
                }
                var identifyResponse = new GameEventIdentifyObjectResponse(player.Session, action.ObjectId, obj);
                player.Session.Network.EnqueueSend(identifyResponse);
                break;
            }

            case GameActionType.Buy:
            {
                // todo: lots, need vendor list, money checks, etc.

                var money            = new GameMessagePrivateUpdatePropertyInt(player.Session, PropertyInt.CoinValue, 4000);
                var sound            = new GameMessageSound(player.Guid, Sound.PickUpItem, 1);
                var sendUseDoneEvent = new GameEventUseDone(player.Session);
                player.Session.Network.EnqueueSend(money, sound, sendUseDoneEvent);

                // send updated vendor inventory.
                player.Session.Network.EnqueueSend(new GameEventApproachVendor(player.Session, action.ObjectId));

                // this is just some testing code for now.
                foreach (ItemProfile item in action.ProfileItems)
                {
                    // todo: something with vendor id and profile list... iid list from vendor dbs.
                    // todo: something with amounts..

                    if (item.Iid == 5)
                    {
                        while (item.Amount > 0)
                        {
                            item.Amount--;
                            WorldObject loot = LootGenerationFactory.CreateTestWorldObject(5090);
                            LootGenerationFactory.AddToContainer(loot, player);
                            player.TrackObject(loot);
                        }
                        var rudecomment = "Who do you think you are, Johny Apple Seed ?";
                        var buyrudemsg  = new GameMessageSystemChat(rudecomment, ChatMessageType.Tell);
                        player.Session.Network.EnqueueSend(buyrudemsg);
                    }
                    else if (item.Iid == 10)
                    {
                        while (item.Amount > 0)
                        {
                            item.Amount--;
                            WorldObject loot = LootGenerationFactory.CreateTestWorldObject(30537);
                            LootGenerationFactory.AddToContainer(loot, player);
                            player.TrackObject(loot);
                        }
                        var rudecomment = "That smells awful, Enjoy eating it!";
                        var buyrudemsg  = new GameMessageSystemChat(rudecomment, ChatMessageType.Tell);
                        player.Session.Network.EnqueueSend(buyrudemsg);
                    }
                }
                break;
            }

            case GameActionType.PutItemInContainer:
            {
                var playerGuid    = new ObjectGuid(action.ObjectId);
                var inventoryGuid = new ObjectGuid(action.SecondaryObjectId);

                // Has to be a player so need to check before I make the cast.
                // If he is not a player, something is bad wrong. Og II
                if (playerGuid.IsPlayer())
                {
                    var aPlayer       = (Player)GetWorldObject(playerGuid);
                    var inventoryItem = GetWorldObject(inventoryGuid);

                    float arrivedRadiusSquared = 0.00f;
                    bool  validGuids;
                    if (WithinUseRadius(playerGuid, inventoryGuid, out arrivedRadiusSquared, out validGuids))
                    {
                        aPlayer.NotifyAndAddToInventory(inventoryItem);
                    }
                    else
                    {
                        if (validGuids)
                        {
                            aPlayer.SetDestinationInformation(inventoryItem.PhysicsData.Position, arrivedRadiusSquared);
                            aPlayer.BlockedGameAction = action;
                            aPlayer.OnAutonomousMove(inventoryItem.PhysicsData.Position,
                                                     aPlayer.Sequences, MovementTypes.MoveToObject, inventoryGuid);
                        }
                    }
                }
                break;
            }

            case GameActionType.DropItem:
            {
                var g = new ObjectGuid(action.ObjectId);
                if (worldObjects.ContainsKey(g))
                {
                    var playerId    = new ObjectGuid(action.ObjectId);
                    var inventoryId = new ObjectGuid(action.SecondaryObjectId);
                    if (playerId.IsPlayer())
                    {
                        var aPlayer = (Player)worldObjects[playerId];
                        aPlayer.NotifyAndDropItem(inventoryId);
                    }
                }
                break;
            }

            case GameActionType.MovementEvent:
            {
                var         g   = new ObjectGuid(action.ObjectId);
                WorldObject obj = (WorldObject)player;
                if (worldObjects.ContainsKey(g))
                {
                    obj = worldObjects[g];
                }
                var motion = action.Motion;
                HandleMovementEvent(obj, motion);
                break;
            }

            case GameActionType.ObjectCreate:
            {
                AddWorldObject(action.WorldObject);
                break;
            }

            case GameActionType.ObjectDelete:
            {
                RemoveWorldObject(action.WorldObject.Guid, false);
                break;
            }

            case GameActionType.QueryHealth:
            {
                if (action.ObjectId == 0)
                {
                    // Deselect the formerly selected Target
                    player.SelectedTarget = 0;
                    break;
                }

                object target   = null;
                var    targetId = new ObjectGuid(action.ObjectId);

                // Remember the selected Target
                player.SelectedTarget = action.ObjectId;

                // TODO: once items are implemented check if there are items that can trigger
                //       the QueryHealth event. So far I believe it only gets triggered for players and creatures
                if (targetId.IsPlayer() || targetId.IsCreature())
                {
                    if (worldObjects.ContainsKey(targetId))
                    {
                        target = worldObjects[targetId];
                    }

                    if (target == null)
                    {
                        // check adjacent landblocks for the targetId
                        foreach (var block in adjacencies)
                        {
                            if (block.Value != null)
                            {
                                if (block.Value.worldObjects.ContainsKey(targetId))
                                {
                                    target = block.Value.worldObjects[targetId];
                                }
                            }
                        }
                    }
                    if (target != null)
                    {
                        float healthPercentage = 0;

                        if (targetId.IsPlayer())
                        {
                            Player tmpTarget = (Player)target;
                            healthPercentage = (float)tmpTarget.Health.Current / (float)tmpTarget.Health.MaxValue;
                        }
                        if (targetId.IsCreature())
                        {
                            Creature tmpTarget = (Creature)target;
                            healthPercentage = (float)tmpTarget.Health.Current / (float)tmpTarget.Health.MaxValue;
                        }
                        var updateHealth = new GameEventUpdateHealth(player.Session, targetId.Full, healthPercentage);
                        player.Session.Network.EnqueueSend(updateHealth);
                    }
                }

                break;
            }

            case GameActionType.Use:
            {
                var g = new ObjectGuid(action.ObjectId);
                if (worldObjects.ContainsKey(g))
                {
                    WorldObject obj = worldObjects[g];

                    if ((obj.DescriptionFlags & ObjectDescriptionFlag.LifeStone) != 0)
                    {
                        (obj as Lifestone).OnUse(player);
                    }
                    else if ((obj.DescriptionFlags & ObjectDescriptionFlag.Portal) != 0)
                    {
                        // TODO: When Physics collisions are implemented, this logic should be switched there, as normal portals are not onUse.
                        (obj as Portal).OnCollide(player);
                    }
                    else if ((obj.DescriptionFlags & ObjectDescriptionFlag.Door) != 0)
                    {
                        (obj as Door).OnUse(player);
                    }

                    else if ((obj.DescriptionFlags & ObjectDescriptionFlag.Vendor) != 0)
                    {
                        (obj as Vendor).OnUse(player);
                    }

                    // switch (obj.Type)
                    // {
                    //    case Enum.ObjectType.Portal:
                    //        {
                    //            // TODO: When Physics collisions are implemented, this logic should be switched there, as normal portals are not onUse.
                    //
                    //            (obj as Portal).OnCollide(player);
                    //
                    //            break;
                    //        }
                    //    case Enum.ObjectType.LifeStone:
                    //        {
                    //            (obj as Lifestone).OnUse(player);
                    //            break;
                    //        }
                    // }
                }
                break;
            }
            }
        }
Пример #25
0
        /// <summary>
        /// Broadcasts the player death animation, updates vitae, and sends network messages for player death
        /// Queues the action to call TeleportOnDeath and enter portal space soon
        /// </summary>
        protected override void Die(WorldObject lastDamager, WorldObject topDamager)
        {
            UpdateVital(Health, 0);
            NumDeaths++;
            DeathLevel  = Level; // for calculating vitae XP
            VitaeCpPool = 0;     // reset vitae XP earned

            // killer = top damager for looting rights
            if (topDamager != null)
            {
                Killer = topDamager.Guid.Full;
            }

            // broadcast death animation
            var deathAnim = new UniversalMotion(MotionStance.NonCombat, new MotionItem(MotionCommand.Dead));

            EnqueueBroadcastMotion(deathAnim);

            // killer death message = last damager
            var killerMsg           = lastDamager != null ? " to " + lastDamager.Name : "";
            var currentDeathMessage = $"died{killerMsg}.";

            // create network messages for player death
            var msgHealthUpdate = new GameMessagePrivateUpdateAttribute2ndLevel(this, Vital.Health, 0);

            // TODO: death sounds? seems to play automatically in client
            // var msgDeathSound = new GameMessageSound(Guid, Sound.Death1, 1.0f);
            var msgYourDeath         = new GameEventYourDeath(Session, $"You have {currentDeathMessage}");
            var msgNumDeaths         = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.NumDeaths, NumDeaths ?? 0);
            var msgDeathLevel        = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.DeathLevel, DeathLevel ?? 0);
            var msgVitaeCpPool       = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.VitaeCpPool, VitaeCpPool.Value);
            var msgPurgeEnchantments = new GameEventPurgeAllEnchantments(Session);

            // update vitae
            var vitae = EnchantmentManager.UpdateVitae();

            var spellID             = (uint)Network.Enum.Spell.Vitae;
            var spellBase           = DatManager.PortalDat.SpellTable.Spells[spellID];
            var spell               = DatabaseManager.World.GetCachedSpell(spellID);
            var vitaeEnchantment    = new Enchantment(this, Guid, spellID, (double)spell.Duration, 0, spell.StatModType, vitae);
            var msgVitaeEnchantment = new GameEventMagicUpdateEnchantment(Session, vitaeEnchantment);

            // send network messages for player death
            Session.Network.EnqueueSend(msgHealthUpdate, msgYourDeath, msgNumDeaths, msgDeathLevel, msgVitaeCpPool, msgPurgeEnchantments, msgVitaeEnchantment);

            // wait for the death animation to finish
            var dieChain   = new ActionChain();
            var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.Dead);

            dieChain.AddDelaySeconds(animLength + 1.0f);

            // enter portal space
            dieChain.AddAction(this, CreateCorpse);
            dieChain.AddAction(this, TeleportOnDeath);
            dieChain.EnqueueChain();

            // if the player's lifestone is in a different landblock, also broadcast their demise to that landblock
            if (Sanctuary != null && Location.Landblock != Sanctuary.Landblock)
            {
                var killerGuid = lastDamager != null ? lastDamager.Guid : Guid;
                ActionBroadcastKill($"{Name} has {currentDeathMessage}", Guid, killerGuid);
            }
            DamageHistory.Reset();
        }
Пример #26
0
        /// <summary>
        /// Recalls you to your allegiance's Mansion or Villa
        /// </summary>
        public void HandleActionTeleToMansion()
        {
            //Console.WriteLine($"{Name}.HandleActionTeleToMansion()");

            if (RecallsDisabled)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.ExitTrainingAcademyToUseCommand));
                return;
            }

            // check if player is in an allegiance
            if (Allegiance == null)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNotInAllegiance));
                return;
            }

            var allegianceHouse = Allegiance.GetHouse();

            if (allegianceHouse == null)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchDoesNotOwnAMansionOrVilla));
                return;
            }

            if (allegianceHouse.HouseType < ACE.Entity.Enum.HouseType.Villa)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchsHouseIsNotAMansionOrVilla));
                return;
            }

            // ensure allegiance housing has allegiance permissions enabled
            if (allegianceHouse.MonarchId == null)
            {
                Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchHasClosedTheMansion));
                return;
            }

            if (CombatMode != CombatMode.NonCombat)
            {
                // this should be handled by a different thing, probably a function that forces player into peacemode
                var updateCombatMode = new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.CombatMode, (int)CombatMode.NonCombat);
                SetCombatMode(CombatMode.NonCombat);
                Session.Network.EnqueueSend(updateCombatMode);
            }

            EnqueueBroadcast(new GameMessageSystemChat($"{Name} is recalling to the Allegiance housing.", ChatMessageType.Recall), 96.0f);
            EnqueueBroadcastMotion(motionHouseRecall);

            var startPos = new Position(Location);

            // Wait for animation
            var actionChain = new ActionChain();

            // Then do teleport
            var animLength = DatManager.PortalDat.ReadFromDat <MotionTable>(MotionTableId).GetAnimationLength(MotionCommand.HouseRecall);

            actionChain.AddDelaySeconds(animLength);
            actionChain.AddAction(this, () =>
            {
                var endPos = new Position(Location);
                if (startPos.SquaredDistanceTo(endPos) > RecallMoveThresholdSq)
                {
                    Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouHaveMovedTooFar));
                    return;
                }

                Teleport(allegianceHouse.SlumLord.Location);
            });

            actionChain.EnqueueChain();
        }