diff --git a/Source/Framework/Constants/CliDBConst.cs b/Source/Framework/Constants/CliDBConst.cs index 8a6b5c29a..54542ae23 100644 --- a/Source/Framework/Constants/CliDBConst.cs +++ b/Source/Framework/Constants/CliDBConst.cs @@ -2115,13 +2115,20 @@ namespace Framework.Constants DontDismissWhenEncounterIsAborted = 0x80000000 // NYI } + [Flags] public enum TaxiNodeFlags : int { - Alliance = 0x1, - Horde = 0x2, - UseFavoriteMount = 0x10 + ShowOnAllianceMap = 0x01, + ShowOnHordeMap = 0x02, + ShowOnMapBorder = 0x04, + ShowIfClientPassesCondition = 0x08, + UsePlayerFavoriteMount = 0x10, + EndPointPnly = 0x20, + IgnoreForFindNearest = 0x40, + DoNotShowInWorldMapUI = 0x80, } + [Flags] public enum TaxiPathNodeFlags : int { Teleport = 0x1, diff --git a/Source/Game/DataStorage/CliDB.cs b/Source/Game/DataStorage/CliDB.cs index b340f6ad6..34b5725e3 100644 --- a/Source/Game/DataStorage/CliDB.cs +++ b/Source/Game/DataStorage/CliDB.cs @@ -413,7 +413,7 @@ namespace Game.DataStorage foreach (var node in TaxiNodesStorage.Values) { - if (!node.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde)) + if (!node.IsPartOfTaxiNetwork()) continue; // valid taxi network node @@ -421,9 +421,9 @@ namespace Game.DataStorage byte submask = (byte)(1 << (int)((node.Id - 1) % 8)); TaxiNodesMask[field] |= submask; - if (node.Flags.HasAnyFlag(TaxiNodeFlags.Horde)) + if (node.GetFlags().HasFlag(TaxiNodeFlags.ShowOnHordeMap)) HordeTaxiNodesMask[field] |= submask; - if (node.Flags.HasAnyFlag(TaxiNodeFlags.Alliance)) + if (node.GetFlags().HasFlag(TaxiNodeFlags.ShowOnAllianceMap)) AllianceTaxiNodesMask[field] |= submask; int uiMapId; diff --git a/Source/Game/DataStorage/Structs/T_Records.cs b/Source/Game/DataStorage/Structs/T_Records.cs index 8c60d61fc..3b3d76459 100644 --- a/Source/Game/DataStorage/Structs/T_Records.cs +++ b/Source/Game/DataStorage/Structs/T_Records.cs @@ -36,13 +36,29 @@ namespace Game.DataStorage public ushort ContinentID; public uint ConditionID; public ushort CharacterBitNumber; - public TaxiNodeFlags Flags; + public int Flags; public int UiTextureKitID; public int MinimapAtlasMemberID; public float Facing; public uint SpecialIconConditionID; public uint VisibilityConditionID; public uint[] MountCreatureID = new uint[2]; + + public TaxiNodeFlags GetFlags() { return (TaxiNodeFlags)Flags; } + + public bool IsPartOfTaxiNetwork() + { + return GetFlags().HasFlag(TaxiNodeFlags.ShowOnAllianceMap | TaxiNodeFlags.ShowOnHordeMap) + // manually whitelisted nodes + || Id == 1985 // [Hidden] Argus Ground Points Hub (Ground TP out to here, TP to Vindicaar from here) + || Id == 1986 // [Hidden] Argus Vindicaar Ground Hub (Vindicaar TP out to here, TP to ground from here) + || Id == 1987 // [Hidden] Argus Vindicaar No Load Hub (Vindicaar No Load transition goes through here) + || Id == 2627 // [Hidden] 9.0 Bastion Ground Points Hub (Ground TP out to here, TP to Sanctum from here) + || Id == 2628 // [Hidden] 9.0 Bastion Ground Hub (Sanctum TP out to here, TP to ground from here) + || Id == 2732 // [HIDDEN] 9.2 Resonant Peaks - Teleport Network - Hidden Hub (Connects all Nodes to each other without unique paths) + || Id == 2835 // [Hidden] 10.0 Travel Network - Destination Input + || Id == 2843; // [Hidden] 10.0 Travel Network - Destination Output + } } public sealed class TaxiPathRecord diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 379167692..bfb7c4ac7 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -358,7 +358,15 @@ namespace Game.Entities // checked and error show at loading templates var factionTemplate = CliDB.FactionTemplateStorage.LookupByKey(cInfo.Faction); if (factionTemplate != null) + { SetPvP(factionTemplate.Flags.HasAnyFlag((ushort)FactionTemplateFlags.PVP)); + if (IsTaxi()) + { + uint taxiNodesId = Global.ObjectMgr.GetNearestTaxiNode(GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId(), + (factionTemplate.FactionGroup & (byte)FactionMasks.Alliance) != 0 ? Team.Alliance : Team.Horde); + SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.TaxiNodesID), (int)taxiNodesId); + } + } // updates spell bars for vehicles and set player's faction - should be called here, to overwrite faction that is set from the new template if (IsVehicle()) @@ -407,7 +415,7 @@ namespace Game.Entities // Apply all other side effects of flag changes SetTemplateRooted(flags.HasFlag(CreatureStaticFlags.Sessile)); } - + public override void Update(uint diff) { if (IsAIEnabled() && triggerJustAppeared && m_deathState != DeathState.Dead) @@ -1132,14 +1140,14 @@ namespace Game.Entities SummonCreature(npcEntry, GetPosition(), TempSummonType.TimedDespawn, TimeSpan.FromSeconds(1), 0, 0); } - public bool HasFlag(CreatureStaticFlags flag) { return _staticFlags.HasFlag(flag); } - public bool HasFlag(CreatureStaticFlags2 flag) { return _staticFlags.HasFlag(flag); } - public bool HasFlag(CreatureStaticFlags3 flag) { return _staticFlags.HasFlag(flag); } - public bool HasFlag(CreatureStaticFlags4 flag) { return _staticFlags.HasFlag(flag); } - public bool HasFlag(CreatureStaticFlags5 flag) { return _staticFlags.HasFlag(flag); } - public bool HasFlag(CreatureStaticFlags6 flag) { return _staticFlags.HasFlag(flag); } - public bool HasFlag(CreatureStaticFlags7 flag) { return _staticFlags.HasFlag(flag); } - public bool HasFlag(CreatureStaticFlags8 flag) { return _staticFlags.HasFlag(flag); } + public bool HasFlag(CreatureStaticFlags flag) { return _staticFlags.HasFlag(flag); } + public bool HasFlag(CreatureStaticFlags2 flag) { return _staticFlags.HasFlag(flag); } + public bool HasFlag(CreatureStaticFlags3 flag) { return _staticFlags.HasFlag(flag); } + public bool HasFlag(CreatureStaticFlags4 flag) { return _staticFlags.HasFlag(flag); } + public bool HasFlag(CreatureStaticFlags5 flag) { return _staticFlags.HasFlag(flag); } + public bool HasFlag(CreatureStaticFlags6 flag) { return _staticFlags.HasFlag(flag); } + public bool HasFlag(CreatureStaticFlags7 flag) { return _staticFlags.HasFlag(flag); } + public bool HasFlag(CreatureStaticFlags8 flag) { return _staticFlags.HasFlag(flag); } public uint GetGossipMenuId() { @@ -1150,7 +1158,7 @@ namespace Game.Entities { _gossipMenuId = gossipMenuId; } - + public uint GetTrainerId() { if (_trainerId.HasValue) @@ -1202,7 +1210,7 @@ namespace Game.Entities } public bool IsTapListNotClearedOnEvade() { return m_dontClearTapListOnEvade; } - + public void SetTappedBy(Unit unit, bool withGroup = true) { // set the player whose group should receive the right @@ -1285,7 +1293,7 @@ namespace Game.Entities public bool CanHaveLoot() { return !_staticFlags.HasFlag(CreatureStaticFlags.NoLoot); } public void SetCanHaveLoot(bool canHaveLoot) { _staticFlags.ApplyFlag(CreatureStaticFlags.NoLoot, !canHaveLoot); } - + public void SaveToDB() { // this should only be used when the creature has already been loaded @@ -1633,7 +1641,7 @@ namespace Game.Entities return true; } - + bool CreateFromProto(ulong guidlow, uint entry, CreatureData data = null, uint vehId = 0) { SetZoneScript(); @@ -1740,7 +1748,7 @@ namespace Game.Entities } public bool IsTemplateRooted() { return _staticFlags.HasFlag(CreatureStaticFlags.Sessile); } - + public void SetTemplateRooted(bool rooted) { _staticFlags.ApplyFlag(CreatureStaticFlags.Sessile, rooted); @@ -3295,7 +3303,7 @@ namespace Game.Entities return (CreatureAI)i_AI; } - public T GetAI() where T : CreatureAI + public new T GetAI() where T : CreatureAI { return (T)i_AI; } @@ -3308,7 +3316,7 @@ namespace Game.Entities public void SetCanMelee(bool canMelee) { _staticFlags.ApplyFlag(CreatureStaticFlags.NoMelee, !canMelee); } public bool CanIgnoreLineOfSightWhenCastingOnMe() { return _staticFlags.HasFlag(CreatureStaticFlags4.IgnoreLosWhenCastingOnMe); } - + public sbyte GetOriginalEquipmentId() { return m_originalEquipmentId; } public byte GetCurrentEquipmentId() { return m_equipmentId; } public void SetCurrentEquipmentId(byte id) { m_equipmentId = id; } @@ -3316,7 +3324,7 @@ namespace Game.Entities public CreatureTemplate GetCreatureTemplate() { return m_creatureInfo; } public CreatureData GetCreatureData() { return m_creatureData; } public CreatureDifficulty GetCreatureDifficulty() { return m_creatureDifficulty; } - + public override bool LoadFromDB(ulong spawnId, Map map, bool addToMap, bool allowDuplicate) { if (!allowDuplicate) @@ -3458,7 +3466,7 @@ namespace Game.Entities bool CanRegenerateHealth() { return !_staticFlags.HasFlag(CreatureStaticFlags5.NoHealthRegen) && _regenerateHealth; } public void SetRegenerateHealth(bool value) { _staticFlags.ApplyFlag(CreatureStaticFlags5.NoHealthRegen, !value); } - + public void SetHomePosition(float x, float y, float z, float o) { m_homePosition.Relocate(x, y, z, o); diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 14baa426c..ceaa474c6 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -7036,7 +7036,7 @@ namespace Game.Entities // change but I couldn't find a suitable alternative. OK to use class because only DK // can use this taxi. uint mount_display_id; - if (node.Flags.HasAnyFlag(TaxiNodeFlags.UseFavoriteMount) && preferredMountDisplay != 0) + if (node.GetFlags().HasFlag(TaxiNodeFlags.UsePlayerFavoriteMount) && preferredMountDisplay != 0) mount_display_id = preferredMountDisplay; else mount_display_id = ObjectMgr.GetTaxiMountDisplayId(sourcenode, GetTeam(), npc == null || (sourcenode == 315 && GetClass() == Class.Deathknight)); diff --git a/Source/Game/Entities/Taxi/TaxiPathGraph.cs b/Source/Game/Entities/Taxi/TaxiPathGraph.cs index 7595084a2..b495590f9 100644 --- a/Source/Game/Entities/Taxi/TaxiPathGraph.cs +++ b/Source/Game/Entities/Taxi/TaxiPathGraph.cs @@ -84,7 +84,7 @@ namespace Game.Entities static uint GetVertexIDFromNodeID(TaxiNodesRecord node) { - return m_verticesByNode.ContainsKey(node.Id) ? m_verticesByNode[node.Id] : uint.MaxValue; + return m_verticesByNode.LookupByKey(node.Id); } static uint GetNodeIDFromVertexID(uint vertexID) @@ -107,7 +107,7 @@ namespace Game.Entities { TaxiNodesRecord from = CliDB.TaxiNodesStorage.LookupByKey(path.FromTaxiNode); TaxiNodesRecord to = CliDB.TaxiNodesStorage.LookupByKey(path.ToTaxiNode); - if (from != null && to != null && from.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde) && to.Flags.HasAnyFlag(TaxiNodeFlags.Alliance | TaxiNodeFlags.Horde)) + if (from != null && to != null && from.IsPartOfTaxiNetwork() && to.IsPartOfTaxiNetwork()) AddVerticeAndEdgeFromNodeInfo(from, to, path.Id, edges); } @@ -141,25 +141,30 @@ namespace Game.Entities else { shortestPath.Clear(); - // We want to use Dijkstra on this graph - DijkstraShortestPath g = new(m_graph, (int)GetVertexIDFromNodeID(from)); - var path = g.PathTo((int)GetVertexIDFromNodeID(to)); - // found a path to the goal - shortestPath.Add(from.Id); - foreach (var edge in path) - { - //todo test me No clue about this.... - var To = m_nodesByVertex[(int)edge.To]; - TaxiNodeFlags requireFlag = (player.GetTeam() == Team.Alliance) ? TaxiNodeFlags.Alliance : TaxiNodeFlags.Horde; - if (!To.Flags.HasAnyFlag(requireFlag)) - continue; - PlayerConditionRecord condition = CliDB.PlayerConditionStorage.LookupByKey(To.ConditionID); - if (condition != null) - if (!ConditionManager.IsPlayerMeetingCondition(player, condition)) + uint fromVertexId = GetVertexIDFromNodeID(from); + uint toVertexId = GetVertexIDFromNodeID(to); + if (fromVertexId != 0 && toVertexId != 0) + { + // We want to use Dijkstra on this graph + DijkstraShortestPath dijkstra = new(m_graph, (int)fromVertexId); + var path = dijkstra.PathTo((int)toVertexId); + // found a path to the goal + shortestPath.Add(from.Id); + foreach (var edge in path) + { + var To = m_nodesByVertex[(int)edge.To]; + TaxiNodeFlags requireFlag = (player.GetTeam() == Team.Alliance) ? TaxiNodeFlags.ShowOnAllianceMap : TaxiNodeFlags.ShowOnHordeMap; + if (!To.GetFlags().HasFlag(requireFlag)) continue; - shortestPath.Add(GetNodeIDFromVertexID(edge.To)); + PlayerConditionRecord condition = CliDB.PlayerConditionStorage.LookupByKey(To.ConditionID); + if (condition != null) + if (!ConditionManager.IsPlayerMeetingCondition(player, condition)) + continue; + + shortestPath.Add(GetNodeIDFromVertexID(edge.To)); + } } } @@ -169,7 +174,11 @@ namespace Game.Entities //todo test me public static void GetReachableNodesMask(TaxiNodesRecord from, byte[] mask) { - DepthFirstSearch depthFirst = new(m_graph, GetVertexIDFromNodeID(from), vertex => + uint vertexId = GetVertexIDFromNodeID(from); + if (vertexId == 0) + return; + + DepthFirstSearch depthFirst = new(m_graph, vertexId, vertex => { TaxiNodesRecord taxiNode = CliDB.TaxiNodesStorage.LookupByKey(GetNodeIDFromVertexID(vertex)); if (taxiNode != null) diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 911901c12..b7e06b32f 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -10398,11 +10398,11 @@ namespace Game float dist = 10000; uint id = 0; - TaxiNodeFlags requireFlag = (team == Team.Alliance) ? TaxiNodeFlags.Alliance : TaxiNodeFlags.Horde; + TaxiNodeFlags requireFlag = (team == Team.Alliance) ? TaxiNodeFlags.ShowOnAllianceMap : TaxiNodeFlags.ShowOnHordeMap; foreach (var node in CliDB.TaxiNodesStorage.Values) { var i = node.Id; - if (node.ContinentID != mapid || !node.Flags.HasAnyFlag(requireFlag)) + if (node.ContinentID != mapid || !node.GetFlags().HasFlag(requireFlag) || node.GetFlags().HasFlag(TaxiNodeFlags.IgnoreForFindNearest)) continue; uint field = (i - 1) / 8; diff --git a/Source/Game/Handlers/MovementHandler.cs b/Source/Game/Handlers/MovementHandler.cs index 7320adc42..077852f5e 100644 --- a/Source/Game/Handlers/MovementHandler.cs +++ b/Source/Game/Handlers/MovementHandler.cs @@ -750,17 +750,27 @@ namespace Game TaxiNodesRecord curDestNode = CliDB.TaxiNodesStorage.LookupByKey(curDest); // far teleport case - if (curDestNode != null && curDestNode.ContinentID != GetPlayer().GetMapId() && GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) + if (GetPlayer().GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Flight) { FlightPathMovementGenerator flight = GetPlayer().GetMotionMaster().GetCurrentMovementGenerator() as FlightPathMovementGenerator; if (flight != null) { - // short preparations to continue flight - flight.SetCurrentNodeAfterTeleport(); - TaxiPathNodeRecord node = flight.GetPath()[(int)flight.GetCurrentNode()]; - flight.SkipCurrentNode(); + bool shouldTeleport = curDestNode != null && curDestNode.ContinentID != GetPlayer().GetMapId(); + if (!shouldTeleport) + { + var currentNode = flight.GetPath()[(int)flight.GetCurrentNode()]; + shouldTeleport = currentNode.Flags.HasFlag(TaxiPathNodeFlags.Teleport); + } - GetPlayer().TeleportTo(curDestNode.ContinentID, node.Loc.X, node.Loc.Y, node.Loc.Z, GetPlayer().GetOrientation()); + if (shouldTeleport) + { + // short preparations to continue flight + flight.SetCurrentNodeAfterTeleport(); + var node = flight.GetPath()[(int)flight.GetCurrentNode()]; + flight.SkipCurrentNode(); + + GetPlayer().TeleportTo(curDestNode.ContinentID, node.Loc.X, node.Loc.Y, node.Loc.Z, GetPlayer().GetOrientation()); + } } } diff --git a/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs b/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs index 9e816f2a2..a26f966e8 100644 --- a/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs +++ b/Source/Game/Movement/Generators/FlightPathMovementGenerator.cs @@ -55,7 +55,7 @@ namespace Game.Movement Vector3 vertice = new(_path[i].Loc.X, _path[i].Loc.Y, _path[i].Loc.Z); init.Path().Add(vertice); } - + init.SetFirstPointId((int)GetCurrentNode()); init.SetFly(); init.SetSmooth(); @@ -127,14 +127,17 @@ namespace Game.Movement owner.Dismount(); owner.RemoveUnitFlag(UnitFlags.RemoveClientControl | UnitFlags.OnTaxi); - if (owner.m_taxi.Empty()) + + // update z position to ground and orientation for landing point + // this prevent cheating with landing point at lags + // when client side flight end early in comparison server side + owner.StopMoving(); + + // When the player reaches the last flight point, teleport to destination taxi node location + if (!_path.Empty() && (_path.Count < 2 || !_path[_path.Count - 2].Flags.HasFlag(TaxiPathNodeFlags.Teleport))) { - // update z position to ground and orientation for landing point - // this prevent cheating with landing point at lags - // when client side flight end early in comparison server side - owner.StopMoving(); - // When the player reaches the last flight point, teleport to destination taxi node location - var node = CliDB.TaxiNodesStorage.LookupByKey(taxiNodeId); + var lastPath = CliDB.TaxiPathStorage.LookupByKey(_path.Last().PathID); + var node = CliDB.TaxiNodesStorage.LookupByKey(lastPath.ToTaxiNode); if (node != null) { owner.SetFallInformation(0, node.Pos.Z); @@ -155,6 +158,8 @@ namespace Game.Movement { if (_path[i].ContinentID != curMapId) return (uint)i; + if (i > 0 && _path[i - 1].Flags.HasFlag(TaxiPathNodeFlags.Teleport)) + return (uint)i; } return (uint)_path.Count; @@ -162,7 +167,10 @@ namespace Game.Movement bool IsNodeIncludedInShortenedPath(TaxiPathNodeRecord p1, TaxiPathNodeRecord p2) { - return p1.ContinentID != p2.ContinentID || Math.Pow(p1.Loc.X - p2.Loc.X, 2) + Math.Pow(p1.Loc.Y - p2.Loc.Y, 2) > (40.0f * 40.0f); + return p1.ContinentID != p2.ContinentID + || MathF.Pow(p1.Loc.X - p2.Loc.X, 2) + MathF.Pow(p1.Loc.Y - p2.Loc.Y, 2) > 40.0f * 40.0f + || p2.Flags.HasFlag(TaxiPathNodeFlags.Teleport) + || (p2.Flags.HasFlag(TaxiPathNodeFlags.Stop) && p2.Delay != 0); } public void LoadPath(Player player, uint startNode = 0) @@ -191,7 +199,8 @@ namespace Game.Movement if (passedPreviousSegmentProximityCheck || src == 0 || _path.Empty() || IsNodeIncludedInShortenedPath(_path.Last(), nodes[i])) { if ((src == 0 || (IsNodeIncludedInShortenedPath(start, nodes[i]) && i >= 2)) && - (dst == taxi.Count - 1 || (IsNodeIncludedInShortenedPath(end, nodes[i]) && i < nodes.Length - 1))) + (dst == taxi.Count - 1 || (IsNodeIncludedInShortenedPath(end, nodes[i]) && (i < nodes.Length - 1 || _path.Empty()))) && + (!nodes[i].Flags.HasFlag(TaxiPathNodeFlags.Teleport) || _path.Empty() || !_path.Last().Flags.HasFlag(TaxiPathNodeFlags.Teleport))) // skip consecutive teleports, only keep the first one { passedPreviousSegmentProximityCheck = true; _path.Add(nodes[i]); @@ -205,7 +214,7 @@ namespace Game.Movement } } - _pointsForPathSwitch.Add(new TaxiNodeChangeInfo((uint)(_path.Count - 1), (long)Math.Ceiling(cost * discount))); + _pointsForPathSwitch.Add(new TaxiNodeChangeInfo((uint)(Math.Max(_path.Count, 1) - 1), (long)Math.Ceiling(cost * discount))); } } @@ -217,7 +226,7 @@ namespace Game.Movement uint map0 = _path[_currentNode].ContinentID; for (int i = _currentNode + 1; i < _path.Count; ++i) { - if (_path[i].ContinentID != map0) + if (_path[i].ContinentID != map0 || _path[i - 1].Flags.HasFlag(TaxiPathNodeFlags.Teleport)) { _currentNode = i; return; @@ -277,13 +286,13 @@ namespace Game.Movement return _path[index].PathID; } - + public override string GetDebugInfo() { return $"Current Node: {GetCurrentNode()}\n{base.GetDebugInfo()}\nStart Path Id: {GetPathId(0)} Path Size: {_path.Count} HasArrived: {HasArrived()} End Grid X: {_endGridX} " + $"End Grid Y: {_endGridY} End Map Id: {_endMapId} Preloaded Target Node: {_preloadTargetNode}"; } - + public override bool GetResetPosition(Unit u, out float x, out float y, out float z) { var node = _path[_currentNode];