diff --git a/Source/Framework/Constants/CreatureConst.cs b/Source/Framework/Constants/CreatureConst.cs index 6b3d39103..15e1bac28 100644 --- a/Source/Framework/Constants/CreatureConst.cs +++ b/Source/Framework/Constants/CreatureConst.cs @@ -131,26 +131,26 @@ namespace Framework.Constants public enum UnitFlags3 : uint { Unk0 = 0x01, - UnconsciousOnDeath = 0x02, // Title Unconscious On Death Description Shows "Unconscious" In Unit Tooltip Instead Of "Dead" - AllowMountedCombat = 0x04, // Title Allow Mounted Combat - GarrisonPet = 0x08, // Title Garrison Pet Description Special Garrison Pet Creatures That Display One Of Favorite Player Battle Pets - This Flag Allows Querying Name And Turns Off Default Battle Pet Behavior - UiCanGetPosition = 0x10, // Title Ui Can Get Position Description Allows Lua Functions Like Unitposition To Always Get The Position Even For Npcs Or Non-Grouped Players + UnconsciousOnDeath = 0x02, // Description Shows "Unconscious" In Unit Tooltip Instead Of "Dead" + AllowMountedCombat = 0x04, + GarrisonPet = 0x08, // Special Garrison Pet Creatures That Display One Of Favorite Player Battle Pets - This Flag Allows Querying Name And Turns Off Default Battle Pet Behavior + UiCanGetPosition = 0x10, // Allows Lua Functions Like Unitposition To Always Get The Position Even For Npcs Or Non-Grouped Players AiObstacle = 0x20, AlternativeDefaultLanguage = 0x40, - SuppressAllNpcFeedback = 0x80, // Title Suppress All Npc Feedback Description Skips Playing Sounds On Left Clicking Npc For All Npcs As Long As Npc With This Flag Is Visible - IgnoreCombat = 0x100, // Title Ignore Combat Description Same As SpellAuraIgnoreCombat - SuppressNpcFeedback = 0x200, // Title Suppress Npc Feedback Description Skips Playing Sounds On Left Clicking Npc + SuppressAllNpcFeedback = 0x80, // Skips Playing Sounds On Left Clicking Npc For All Npcs As Long As Npc With This Flag Is Visible + IgnoreCombat = 0x100, // Same As SpellAuraIgnoreCombat + SuppressNpcFeedback = 0x200, // Skips Playing Sounds On Left Clicking Npc Unk10 = 0x400, Unk11 = 0x800, Unk12 = 0x1000, FakeDead = 0x2000, // Title Show As Dead NoFacingOnInteractAndFastFacingChase = 0x4000, // Causes The Creature To Both Not Change Facing On Interaction And Speeds Up Smooth Facing Changes While Attacking (Clientside) - UntargetableFromUi = 0x8000, // Title Untargetable From Ui Description Cannot Be Targeted From Lua Functions Startattack, Targetunit, Petattack + UntargetableFromUi = 0x8000, // Cannot Be Targeted From Lua Functions Startattack, Targetunit, Petattack NoFacingOnInteractWhileFakeDead = 0x10000, // Prevents Facing Changes While Interacting If Creature Has Flag FakeDead AlreadySkinned = 0x20000, - SuppressAllNpcSounds = 0x40000, // Title Suppress All Npc Sounds Description Skips Playing Sounds On Beginning And End Of Npc Interaction For All Npcs As Long As Npc With This Flag Is Visible - SuppressNpcSounds = 0x80000, // Title Suppress Npc Sounds Description Skips Playing Sounds On Beginning And End Of Npc Interaction - Unk20 = 0x100000, + SuppressAllNpcSounds = 0x40000, // Skips Playing Sounds On Beginning And End Of Npc Interaction For All Npcs As Long As Npc With This Flag Is Visible + SuppressNpcSounds = 0x80000, // Skips Playing Sounds On Beginning And End Of Npc Interaction + AllowInteractionWhileInCombat = 0x100000, //Allows using various NPC functions while in combat (vendor, gossip, questgiver) Unk21 = 0x200000, DontFadeOut = 0x400000, Unk23 = 0x800000, @@ -168,7 +168,7 @@ namespace Framework.Constants IgnoreCombat | SuppressNpcFeedback | Unk10 | Unk11 | Unk12 | /* FakeDead | */ /* NoFacingOnInteractAndFastFacingChase | */ /* UntargetableFromUi | */ /* NoFacingOnInteractWhileFakeDead | */ AlreadySkinned | /* SuppressAllNpcSounds | */ /* SuppressNpcSounds | */ - Unk20 | Unk21 | /* DontFadeOut | */ Unk23 | + AllowInteractionWhileInCombat | Unk21 | /* DontFadeOut | */ Unk23 | ForceHideNameplate | Unk25 | Unk26 | Unk27 | Unk28 | Unk29 | Unk30 | Unk31), // Skip Allowed = (0xffffffff & ~Disallowed) // Skip diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 14d1346f9..7a6d67e13 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -286,6 +286,7 @@ namespace Game.Entities // TODO: migrate these in DB _staticFlags.ApplyFlag(CreatureStaticFlags2.AllowMountedCombat, GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.AllowMountedCombat)); + SetInteractionAllowedInCombat(GetCreatureDifficulty().TypeFlags.HasAnyFlag(CreatureTypeFlags.AllowInteractionWhileInCombat)); SetTreatAsRaidUnit(GetCreatureDifficulty().TypeFlags.HasAnyFlag(CreatureTypeFlags.TreatAsRaidUnit)); return true; @@ -2756,6 +2757,33 @@ namespace Game.Entities m_respawnTime = Math.Max(m_corpseRemoveTime + m_respawnDelay, m_respawnTime); } + public override void SetInteractionAllowedWhileHostile(bool interactionAllowed) + { + _staticFlags.ApplyFlag(CreatureStaticFlags5.InteractWhileHostile, interactionAllowed); + base.SetInteractionAllowedWhileHostile(interactionAllowed); + } + + public override void SetInteractionAllowedInCombat(bool interactionAllowed) + { + _staticFlags.ApplyFlag(CreatureStaticFlags3.AllowInteractionWhileInCombat, interactionAllowed); + base.SetInteractionAllowedInCombat(interactionAllowed); + } + + public override void UpdateNearbyPlayersInteractions() + { + base.UpdateNearbyPlayersInteractions(); + + // If as a result of npcflag updates we stop seeing UNIT_NPC_FLAG_QUESTGIVER then + // we must also send SMSG_QUEST_GIVER_STATUS_MULTIPLE because client will not request it automatically + if (IsQuestGiver()) + { + var sender = (Player receiver) => receiver.PlayerTalkClass.SendQuestGiverStatus(receiver.GetQuestDialogStatus(this), GetGUID()); + + MessageDistDeliverer notifier = new(this, sender, GetVisibilityRange()); + Cell.VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + } + public bool HasScalableLevels() { return m_unitData.ContentTuningID != 0; diff --git a/Source/Game/Entities/Object/Update/UpdateFields.cs b/Source/Game/Entities/Object/Update/UpdateFields.cs index e0516f35f..4898c35b2 100644 --- a/Source/Game/Entities/Object/Update/UpdateFields.cs +++ b/Source/Game/Entities/Object/Update/UpdateFields.cs @@ -2332,16 +2332,25 @@ namespace Game.Entities uint GetViewerDependentNpcFlags(UnitData unitData, int i, Unit unit, Player receiver) { uint npcFlag = unitData.NpcFlags[i]; - if (i == 0) + if (npcFlag != 0) { - Creature creature = unit.ToCreature(); - if (creature != null) + if ((!unit.IsInteractionAllowedInCombat() && unit.IsInCombat()) + || (!unit.IsInteractionAllowedWhileHostile() && unit.IsHostileTo(receiver))) + npcFlag = 0; + else { - if (!receiver.CanSeeGossipOn(creature)) - npcFlag &= ~(uint)(NPCFlags.Gossip | NPCFlags.QuestGiver); + Creature creature = unit.ToCreature(); + if (creature != null) + { + if (i == 0) + { + if (!receiver.CanSeeGossipOn(creature)) + npcFlag &= ~(uint)(NPCFlags.Gossip | NPCFlags.QuestGiver); - if (!receiver.CanSeeSpellClickOn(creature)) - npcFlag &= ~(uint)NPCFlags.SpellClick; + if (!receiver.CanSeeSpellClickOn(creature)) + npcFlag &= ~(uint)NPCFlags.SpellClick; + } + } } } diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index 78b582a91..5a9c8f605 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -2032,6 +2032,14 @@ namespace Game.Entities } case TypeId.Unit: { + Creature questGiverCreature = questgiver.ToCreature(); + if (!questGiverCreature.IsInteractionAllowedWhileHostile() && questGiverCreature.IsHostileTo(this)) + return QuestGiverStatus.None; + + if (!questGiverCreature.IsInteractionAllowedInCombat() && questGiverCreature.IsInCombat()) + return QuestGiverStatus.None; + + CreatureAI ai = questgiver.ToCreature().GetAI(); if (ai != null) { @@ -3220,7 +3228,7 @@ namespace Game.Entities { // need also pet quests case support Creature questgiver = ObjectAccessor.GetCreatureOrPetOrVehicle(this, itr); - if (questgiver == null || questgiver.IsHostileTo(this)) + if (questgiver == null) continue; if (!questgiver.HasNpcFlag(NPCFlags.QuestGiver)) diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 984ac190a..8c901942e 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -5472,7 +5472,10 @@ namespace Game.Entities return null; // not unfriendly/hostile - if (!creature.HasUnitFlag2(UnitFlags2.InteractWhileHostile) && creature.GetReactionTo(this) <= ReputationRank.Unfriendly) + if (!creature.IsInteractionAllowedWhileHostile() && creature.GetReactionTo(this) <= ReputationRank.Unfriendly) + return nullptr; + + if (creature.IsInCombat() && !creature.IsInteractionAllowedInCombat()) return null; // not too far, taken from CGGameUI::SetInteractTarget diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index 46d0ce25e..156b0196b 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -35,6 +35,9 @@ namespace Game.Entities RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnteringCombat); ProcSkillsAndAuras(this, null, new ProcFlagsInit(ProcFlags.EnterCombat), new ProcFlagsInit(ProcFlags.None), ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); + + if (!IsInteractionAllowedInCombat()) + UpdateNearbyPlayersInteractions(); } public virtual void AtExitCombat() @@ -43,6 +46,9 @@ namespace Game.Entities pair.Value.GetBase().CallScriptEnterLeaveCombatHandlers(pair.Value, false); RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.LeavingCombat); + + if (!IsInteractionAllowedInCombat()) + UpdateNearbyPlayersInteractions(); } public virtual void AtEngage(Unit target) { } @@ -1654,5 +1660,46 @@ namespace Game.Entities /// enables / disables combat interaction of this unit /// public void SetIsCombatDisallowed(bool apply) { _isCombatDisallowed = apply; } + + public bool IsInteractionAllowedWhileHostile() + { + return HasUnitFlag2(UnitFlags2.InteractWhileHostile); + } + + public virtual void SetInteractionAllowedWhileHostile(bool interactionAllowed) + { + if (interactionAllowed) + SetUnitFlag2(UnitFlags2.InteractWhileHostile); + else + RemoveUnitFlag2(UnitFlags2.InteractWhileHostile); + + UpdateNearbyPlayersInteractions(); + } + + public bool IsInteractionAllowedInCombat() + { + return HasUnitFlag3(UnitFlags3.AllowInteractionWhileInCombat); + } + + public virtual void SetInteractionAllowedInCombat(bool interactionAllowed) + { + if (interactionAllowed) + SetUnitFlag3(UnitFlags3.AllowInteractionWhileInCombat); + else + RemoveUnitFlag3(UnitFlags3.AllowInteractionWhileInCombat); + + if (IsInCombat()) + UpdateNearbyPlayersInteractions(); + } + + public virtual void UpdateNearbyPlayersInteractions() + { + for (int i = 0; i < m_unitData.NpcFlags.GetSize(); ++i) + if (m_unitData.NpcFlags[i] != 0) + { + m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.NpcFlags, i); + ForceUpdateFieldChange(); + } + } } } diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 808abb1a5..3097eb05d 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -98,7 +98,12 @@ namespace Game if (staticFlags.HasFlag(CreatureStaticFlags3.CannotTurn)) unitFlags2 |= (uint)UnitFlags2.CannotTurn; + if (staticFlags.HasFlag(CreatureStaticFlags5.InteractWhileHostile)) + unitFlags2 |= (uint)UnitFlags2.InteractWhileHostile; + unitFlags3 = data != null && data.unit_flags3.HasValue ? data.unit_flags3.Value : cInfo.UnitFlags3; + if (staticFlags.HasFlag(CreatureStaticFlags3.AllowInteractionWhileInCombat)) + unitFlags3 |= (uint)UnitFlags3.AllowInteractionWhileInCombat; } public static ResponseCodes CheckPlayerName(string name, Locale locale, bool create = false) diff --git a/Source/Game/Handlers/QuestHandler.cs b/Source/Game/Handlers/QuestHandler.cs index 0de78b89e..169893aae 100644 --- a/Source/Game/Handlers/QuestHandler.cs +++ b/Source/Game/Handlers/QuestHandler.cs @@ -17,28 +17,14 @@ namespace Game [WorldPacketHandler(ClientOpcodes.QuestGiverStatusQuery, Processing = PacketProcessing.Inplace)] void HandleQuestgiverStatusQuery(QuestGiverStatusQuery packet) { - QuestGiverStatus questStatus = QuestGiverStatus.None; - - var questgiver = Global.ObjAccessor.GetObjectByTypeMask(GetPlayer(), packet.QuestGiverGUID, TypeMask.Unit | TypeMask.GameObject); - if (questgiver == null) + var questGiver = Global.ObjAccessor.GetObjectByTypeMask(GetPlayer(), packet.QuestGiverGUID, TypeMask.Unit | TypeMask.GameObject); + if (questGiver == null) { Log.outInfo(LogFilter.Network, "Error in CMSG_QUESTGIVER_STATUS_QUERY, called for non-existing questgiver {0}", packet.QuestGiverGUID.ToString()); return; } - switch (questgiver.GetTypeId()) - { - case TypeId.Unit: - if (!questgiver.ToCreature().IsHostileTo(GetPlayer()))// do not show quest status to enemies - questStatus = GetPlayer().GetQuestDialogStatus(questgiver); - break; - case TypeId.GameObject: - questStatus = GetPlayer().GetQuestDialogStatus(questgiver); - break; - default: - Log.outError(LogFilter.Network, "QuestGiver called for unexpected type {0}", questgiver.GetTypeId()); - break; - } + QuestGiverStatus questStatus = _player.GetQuestDialogStatus(questGiver); //inform client about status of quest GetPlayer().PlayerTalkClass.SendQuestGiverStatus(questStatus, packet.QuestGiverGUID); @@ -396,7 +382,7 @@ namespace Game if (GetPlayer().GetQuestStatus(packet.QuestID) != QuestStatus.Complete) return; - + GetPlayer().PlayerTalkClass.SendQuestGiverOfferReward(quest, packet.QuestGiverGUID, true); }