diff --git a/Source/Game/Collision/Callbacks.cs b/Source/Game/Collision/Callbacks.cs index 0b4d71ff5..df50af6b2 100644 --- a/Source/Game/Collision/Callbacks.cs +++ b/Source/Game/Collision/Callbacks.cs @@ -26,6 +26,7 @@ namespace Game.Collision { public virtual void Invoke(Vector3 point, uint entry) { } public virtual void Invoke(Vector3 point, IModel entry) { } + public virtual void Invoke(Vector3 point, GameObjectModel obj) { } public virtual bool Invoke(Ray ray, uint entry, ref float distance, bool pStopAtFirstHit) { return false; } public virtual bool Invoke(Ray r, IModel obj, ref float distance) { return false; } } @@ -106,10 +107,7 @@ namespace Game.Collision } public override bool Invoke(Ray ray, uint entry, ref float distance, bool pStopAtFirstHit) { - bool result = IntersectTriangle(triangles[(int)entry], vertices, ray, ref distance); - if (result) - hit = true; - + hit = IntersectTriangle(triangles[(int)entry], vertices, ray, ref distance) || hit; return hit; } @@ -266,4 +264,25 @@ namespace Game.Collision PhaseShift _phaseShift; AreaInfo _areaInfo; } + + public class DynamicTreeLocationInfoCallback : WorkerCallback + { + public DynamicTreeLocationInfoCallback(PhaseShift phaseShift) + { + _phaseShift = phaseShift; + } + + public override void Invoke(Vector3 p, GameObjectModel obj) + { + if (obj.GetLocationInfo(p, _locationInfo, _phaseShift)) + _hitModel = obj; + } + + public LocationInfo GetLocationInfo() { return _locationInfo; } + public GameObjectModel GetHitModel() { return _hitModel; } + + PhaseShift _phaseShift; + LocationInfo _locationInfo; + GameObjectModel _hitModel; + } } diff --git a/Source/Game/Collision/DynamicTree.cs b/Source/Game/Collision/DynamicTree.cs index 185a0112d..a982d5de2 100644 --- a/Source/Game/Collision/DynamicTree.cs +++ b/Source/Game/Collision/DynamicTree.cs @@ -148,6 +148,31 @@ namespace Game.Collision return false; } + public AreaAndLiquidData GetAreaAndLiquidData(float x, float y, float z, PhaseShift phaseShift, byte reqLiquidType) + { + AreaAndLiquidData data = new(); + + Vector3 v = new(x, y, z +0.5f); + DynamicTreeLocationInfoCallback intersectionCallBack = new(phaseShift); + impl.IntersectPoint(v, intersectionCallBack); + if (intersectionCallBack.GetLocationInfo().hitModel != null) + { + data.floorZ = intersectionCallBack.GetLocationInfo().ground_Z; + uint liquidType = intersectionCallBack.GetLocationInfo().hitModel.GetLiquidType(); + float liquidLevel = 0; + if (reqLiquidType == 0 || (Global.DB2Mgr.GetLiquidFlags(liquidType) & reqLiquidType) != 0) + if (intersectionCallBack.GetHitModel().GetLiquidLevel(v, intersectionCallBack.GetLocationInfo(), ref liquidLevel)) + data.liquidInfo.Set(new AreaAndLiquidData.LiquidInfo(liquidType, liquidLevel)); + + data.areaInfo.Set(new AreaAndLiquidData.AreaInfo(intersectionCallBack.GetHitModel().GetNameSetId(), + intersectionCallBack.GetLocationInfo().rootId, + (int)intersectionCallBack.GetLocationInfo().hitModel.GetWmoID(), + intersectionCallBack.GetLocationInfo().hitModel.GetMogpFlags())); + } + + return data; + } + DynTreeImpl impl; } diff --git a/Source/Game/Collision/Management/VMapManager.cs b/Source/Game/Collision/Management/VMapManager.cs index ff8a7e929..298be46b4 100644 --- a/Source/Game/Collision/Management/VMapManager.cs +++ b/Source/Game/Collision/Management/VMapManager.cs @@ -225,7 +225,7 @@ namespace Game.Collision return false; } - public bool GetLiquidLevel(uint mapId, float x, float y, float z, uint reqLiquidType, ref float level, ref float floor, ref uint type) + public bool GetLiquidLevel(uint mapId, float x, float y, float z, uint reqLiquidType, ref float level, ref float floor, ref uint type, ref uint mogpFlags) { if (!Global.DisableMgr.IsDisabledFor(DisableType.VMAP, mapId, null, (byte)DisableFlags.VmapLiquidStatus)) { @@ -239,6 +239,7 @@ namespace Game.Collision floor = info.ground_Z; Cypher.Assert(floor < float.MaxValue); type = info.hitModel.GetLiquidType(); // entry from LiquidType.dbc + mogpFlags = info.hitModel.GetMogpFlags(); if (reqLiquidType != 0 && !Convert.ToBoolean(Global.DB2Mgr.GetLiquidFlags(type) & reqLiquidType)) return false; if (info.hitInstance.GetLiquidLevel(pos, info, ref level)) diff --git a/Source/Game/Collision/Models/GameObjectModel.cs b/Source/Game/Collision/Models/GameObjectModel.cs index d20f0decb..9f834e6fa 100644 --- a/Source/Game/Collision/Models/GameObjectModel.cs +++ b/Source/Game/Collision/Models/GameObjectModel.cs @@ -139,6 +139,51 @@ namespace Game.Collision } } + public bool GetLocationInfo(Vector3 point, LocationInfo info, PhaseShift phaseShift) + { + if (!IsCollisionEnabled() || !owner.IsSpawned() || !IsMapObject()) + return false; + + if (!owner.IsInPhase(phaseShift)) + return false; + + if (!iBound.contains(point)) + return false; + + // child bounds are defined in object space: + Vector3 pModel = iInvRot * (point - iPos) * iInvScale; + Vector3 zDirModel = iInvRot * new Vector3(0.0f, 0.0f, -1.0f); + float zDist; + if (iModel.GetLocationInfo(pModel, zDirModel, out zDist, info)) + { + Vector3 modelGround = pModel + zDist * zDirModel; + float world_Z = ((modelGround * iInvRot) * iScale + iPos).Z; + if (info.ground_Z < world_Z) + { + info.ground_Z = world_Z; + return true; + } + } + + return false; + } + + public bool GetLiquidLevel(Vector3 point, LocationInfo info, ref float liqHeight) + { + // child bounds are defined in object space: + Vector3 pModel = iInvRot * (point - iPos) * iInvScale; + //Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f); + float zDist; + if (info.hitModel.GetLiquidLevel(pModel, out zDist)) + { + // calculate world height (zDist in model coords): + // assume WMO not tilted (wouldn't make much sense anyway) + liqHeight = zDist * iScale + iPos.Z; + return true; + } + return false; + } + public bool UpdatePosition() { if (iModel == null) @@ -177,7 +222,8 @@ namespace Game.Collision public void EnableCollision(bool enable) { _collisionEnabled = enable; } bool IsCollisionEnabled() { return _collisionEnabled; } public bool IsMapObject() { return isWmo; } - + public byte GetNameSetId() { return owner.GetNameSetId(); } + public static void LoadGameObjectModelList() { uint oldMSTime = Time.GetMSTime(); diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index a5552e47c..f83e7d2df 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -3306,8 +3306,9 @@ namespace Game.Entities return; } + float halfHeight = GetCollisionHeight() * 0.5f; UpdateAllowedPositionZ(destx, desty, ref destz); - bool col = Global.VMapMgr.GetObjectHitPos(PhasingHandler.GetTerrainMapId(GetPhaseShift(), GetMap(), pos.posX, pos.posY), pos.posX, pos.posY, pos.posZ, destx, desty, destz, out destx, out desty, out destz, -0.5f); + bool col = Global.VMapMgr.GetObjectHitPos(PhasingHandler.GetTerrainMapId(GetPhaseShift(), GetMap(), pos.posX, pos.posY), pos.posX, pos.posY, pos.posZ + halfHeight, destx, desty, destz + halfHeight, out destx, out desty, out destz, -0.5f); // collision occured if (col) @@ -3319,7 +3320,7 @@ namespace Game.Entities } // check dynamic collision - col = GetMap().GetObjectHitPos(GetPhaseShift(), pos.posX, pos.posY, pos.posZ, destx, desty, destz, out destx, out desty, out destz, -0.5f); + col = GetMap().GetObjectHitPos(GetPhaseShift(), pos.posX, pos.posY, pos.posZ + halfHeight, destx, desty, destz + halfHeight, out destx, out desty, out destz, -0.5f); // Collided with a gameobject if (col) @@ -3363,11 +3364,7 @@ namespace Game.Entities float GetMapWaterOrGroundLevel(float x, float y, float z, ref float ground) { - Unit unit = ToUnit(); - if (unit != null) - return GetMap().GetWaterOrGroundLevel(GetPhaseShift(), x, y, z, ref ground, !unit.HasAuraType(AuraType.WaterWalk), GetCollisionHeight()); - - return z; + return GetMap().GetWaterOrGroundLevel(GetPhaseShift(), x, y, z, ref ground, IsTypeMask(TypeMask.Unit) ? !ToUnit().HasAuraType(AuraType.WaterWalk) : false, GetCollisionHeight()); } public float GetMapHeight(float x, float y, float z, bool vmap = true, float distanceToSearch = MapConst.DefaultHeightSearch) diff --git a/Source/Game/Maps/GridMap.cs b/Source/Game/Maps/GridMap.cs index 55f004c00..6b0b27f14 100644 --- a/Source/Game/Maps/GridMap.cs +++ b/Source/Game/Maps/GridMap.cs @@ -69,6 +69,12 @@ namespace Game.Maps return LoadResult.ReadFromFileFailed; } + if (header.holesSize != 0 && !LoadHolesData(reader, header.holesOffset)) + { + Log.outError(LogFilter.Maps, "Error loading map holes data"); + return LoadResult.ReadFromFileFailed; + } + return LoadResult.Success; } @@ -207,6 +213,15 @@ namespace Game.Maps return true; } + bool LoadHolesData(BinaryReader reader, uint offset) + { + reader.BaseStream.Seek(offset, SeekOrigin.Begin); + + _holes = reader.ReadArray(16 * 16 * 8); + + return true; + } + public ushort GetArea(float x, float y) { if (_areaMap == null) @@ -239,6 +254,9 @@ namespace Game.Maps x_int &= (MapConst.MapResolution - 1); y_int &= (MapConst.MapResolution - 1); + if (IsHole(x_int, y_int)) + return MapConst.InvalidHeight; + float a, b, c; if (x + y < 1) { @@ -304,6 +322,10 @@ namespace Game.Maps y -= y_int; x_int &= (MapConst.MapResolution - 1); y_int &= (MapConst.MapResolution - 1); + + if (IsHole(x_int, y_int)) + return MapConst.InvalidHeight; + int a, b, c; unsafe @@ -377,6 +399,10 @@ namespace Game.Maps y -= y_int; x_int &= (MapConst.MapResolution - 1); y_int &= (MapConst.MapResolution - 1); + + if (IsHole(x_int, y_int)) + return MapConst.InvalidHeight; + int a, b, c; unsafe { @@ -435,6 +461,19 @@ namespace Game.Maps } } + bool IsHole(int row, int col) + { + if (_holes == null) + return false; + + int cellRow = row / 8; // 8 squares per cell + int cellCol = col / 8; + int holeRow = row % 8; + int holeCol = col % 8; + + return (_holes[cellRow * 16 * 8 + cellCol * 8 + holeRow] & (1 << holeCol)) != 0; + } + public float GetMinHeight(float x, float y) { if (_minHeightPlanes == null) @@ -485,19 +524,6 @@ namespace Game.Maps return _liquidMap[cx_int * _liquidWidth + cy_int]; } - // Why does this return LIQUID data? - public LiquidHeaderTypeFlags GetTerrainType(float x, float y) - { - if (_liquidFlags == null) - return LiquidHeaderTypeFlags.NoWater; - - x = 16 * (32 - x / MapConst.SizeofGrids); - y = 16 * (32 - y / MapConst.SizeofGrids); - int lx = (int)x & 15; - int ly = (int)y & 15; - return (LiquidHeaderTypeFlags)_liquidFlags[lx * 16 + ly]; - } - // Get water state on map public ZLiquidStatus GetLiquidStatus(float x, float y, float z, LiquidHeaderTypeFlags? reqLiquidType, LiquidData data, float collisionHeight) { @@ -625,6 +651,8 @@ namespace Game.Maps byte _liquidOffY; byte _liquidWidth; byte _liquidHeight; + + byte[] _holes; #endregion } diff --git a/Source/Game/Maps/Map.cs b/Source/Game/Maps/Map.cs index 4fe17826f..c705f89ae 100644 --- a/Source/Game/Maps/Map.cs +++ b/Source/Game/Maps/Map.cs @@ -1713,7 +1713,7 @@ namespace Game.Maps if (gmap != null) { float gridHeight = gmap.GetHeight(x, y); - if (z > gridHeight) + if (MathFunctions.fuzzyGe(z, gridHeight - MapConst.GroundHeightTolerance)) mapHeight = gridHeight; } @@ -1763,50 +1763,14 @@ namespace Game.Maps return -500.0f; } - private bool IsOutdoorWMO(uint mogpFlags, WMOAreaTableRecord wmoEntry, AreaTableRecord atEntry) + public static bool IsInWMOInterior(uint mogpFlags) { - if (wmoEntry != null && atEntry != null) - { - if (atEntry.Flags.HasFlag(AreaFlags.Outside)) - return true; - if (atEntry.Flags.HasFlag(AreaFlags.Inside)) - return false; - } - - if (wmoEntry != null) - { - if (Convert.ToBoolean(wmoEntry.Flags & 4)) - return true; - if ((wmoEntry.Flags & 2) != 0) - return false; - } - - return (mogpFlags & 0x8) != 0; + return (mogpFlags & 0x2000) != 0; } - public bool IsOutdoors(PhaseShift phaseShift, float x, float y, float z) + private bool GetAreaInfo(PhaseShift phaseShift, float x, float y, float z, out uint mogpflags, out int adtId, out int rootId, out int groupId) { - uint mogpFlags; - int adtId, rootId, groupId; - - // no wmo found? . outside by default - if (!GetAreaInfo(phaseShift, x, y, z, out mogpFlags, out adtId, out rootId, out groupId)) - return true; - - AreaTableRecord atEntry = null; - WMOAreaTableRecord wmoEntry = Global.DB2Mgr.GetWMOAreaTable(rootId, adtId, groupId); - if (wmoEntry != null) - { - Log.outDebug(LogFilter.Maps, "Got WMOAreaTableEntry! flag {0}, areaid {1}", wmoEntry.Flags, - wmoEntry.AreaTableID); - atEntry = CliDB.AreaTableStorage.LookupByKey(wmoEntry.AreaTableID); - } - return IsOutdoorWMO(mogpFlags, wmoEntry, atEntry); - } - - private bool GetAreaInfo(PhaseShift phaseShift, float x, float y, float z, out uint flags, out int adtId, out int rootId, out int groupId) - { - flags = 0; + mogpflags = 0; adtId = 0; rootId = 0; groupId = 0; @@ -1832,7 +1796,7 @@ namespace Game.Maps if (hasDynamicAreaInfo && dynamic_z > vmap_z) { check_z = dynamic_z; - flags = dflags; + mogpflags = dflags; adtId = dadtId; rootId = drootId; groupId = dgroupId; @@ -1840,7 +1804,7 @@ namespace Game.Maps else { check_z = vmap_z; - flags = vflags; + mogpflags = vflags; adtId = vadtId; rootId = vrootId; groupId = vgroupId; @@ -1849,7 +1813,7 @@ namespace Game.Maps else if (hasDynamicAreaInfo) { check_z = dynamic_z; - flags = dflags; + mogpflags = dflags; adtId = dadtId; rootId = drootId; groupId = dgroupId; @@ -1863,7 +1827,8 @@ namespace Game.Maps if (gmap != null) { float mapHeight = gmap.GetHeight(x, y); - if (z > mapHeight && mapHeight > check_z) + // z + 2.0f condition taken from GetHeight(), not sure if it's such a great choice... + if (z + 2.0f > mapHeight && mapHeight > check_z) return false; } return true; @@ -1873,45 +1838,39 @@ namespace Game.Maps } public uint GetAreaId(PhaseShift phaseShift, float x, float y, float z) - { - return GetAreaId(phaseShift, x, y, z, out _); - } - - public uint GetAreaId(PhaseShift phaseShift, float x, float y, float z, out bool isOutdoors) { uint mogpFlags; int adtId, rootId, groupId; - WMOAreaTableRecord wmoEntry = null; - AreaTableRecord atEntry = null; - bool haveAreaInfo = false; + float vmapZ = z; + bool hasVmapArea = GetAreaInfo(phaseShift, x, y, vmapZ, out mogpFlags, out adtId, out rootId, out groupId); + + uint gridAreaId = 0; + float gridMapHeight = MapConst.InvalidHeight; + GridMap gmap = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); + if (gmap != null) + { + gridAreaId = gmap.GetArea(x, y); + gridMapHeight = gmap.GetHeight(x, y); + } + uint areaId = 0; - if (GetAreaInfo(phaseShift, x, y, z, out mogpFlags, out adtId, out rootId, out groupId)) + // floor is the height we are closer to (but only if above) + if (hasVmapArea && MathFunctions.fuzzyGe(z, vmapZ - MapConst.GroundHeightTolerance) && (MathFunctions.fuzzyLt(z, gridMapHeight - MapConst.GroundHeightTolerance) || vmapZ > gridMapHeight)) { - haveAreaInfo = true; - wmoEntry = Global.DB2Mgr.GetWMOAreaTable(rootId, adtId, groupId); + // wmo found + var wmoEntry = Global.DB2Mgr.GetWMOAreaTable(rootId, adtId, groupId); if (wmoEntry != null) - { areaId = wmoEntry.AreaTableID; - atEntry = CliDB.AreaTableStorage.LookupByKey(wmoEntry.AreaTableID); - } + + if (areaId == 0) + areaId = gridAreaId; } + else + areaId = gridAreaId; if (areaId == 0) - { - GridMap gmap = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); - if (gmap != null) - areaId = gmap.GetArea(x, y); - - // this used while not all *.map files generated (instances) - if (areaId == 0) - areaId = i_mapRecord.AreaTableID; - } - - if (haveAreaInfo) - isOutdoors = IsOutdoorWMO(mogpFlags, wmoEntry, atEntry); - else - isOutdoors = true; + areaId = i_mapRecord.AreaTableID; return areaId; } @@ -1942,15 +1901,6 @@ namespace Game.Maps public void GetZoneAndAreaId(PhaseShift phaseShift, out uint zoneid, out uint areaid, Position pos) { GetZoneAndAreaId(phaseShift, out zoneid, out areaid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } - private LiquidHeaderTypeFlags GetTerrainType(PhaseShift phaseShift, float x, float y) - { - GridMap gmap = GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y); - if (gmap != null) - return gmap.GetTerrainType(x, y); - - return LiquidHeaderTypeFlags.NoWater; - } - public ZLiquidStatus GetLiquidStatus(PhaseShift phaseShift, float x, float y, float z, LiquidHeaderTypeFlags reqLiquidType, float collisionHeight = MapConst.DefaultCollesionHeight) { return GetLiquidStatus(phaseShift, x, y, z, reqLiquidType, out _, collisionHeight); @@ -1963,13 +1913,17 @@ namespace Game.Maps float liquid_level = MapConst.InvalidHeight; float ground_level = MapConst.InvalidHeight; uint liquid_type = 0; + uint mogpFlags = 0; + bool useGridLiquid = true; uint terrainMapId = PhasingHandler.GetTerrainMapId(phaseShift, this, x, y); - if (Global.VMapMgr.GetLiquidLevel(terrainMapId, x, y, z, (byte)reqLiquidType, ref liquid_level, ref ground_level, ref liquid_type)) + + if (Global.VMapMgr.GetLiquidLevel(terrainMapId, x, y, z, (byte)reqLiquidType, ref liquid_level, ref ground_level, ref liquid_type, ref mogpFlags)) { + useGridLiquid = !IsInWMOInterior(mogpFlags); Log.outDebug(LogFilter.Maps, "getLiquidStatus(): vmap liquid level: {0} ground: {1} type: {2}", liquid_level, ground_level, liquid_type); // Check water level and ground level - if (liquid_level > ground_level && z > ground_level - 2) + if (liquid_level > ground_level && MathFunctions.fuzzyGe(z, ground_level - MapConst.GroundHeightTolerance)) { // All ok in water . store data // hardcoded in client like this @@ -2022,21 +1976,24 @@ namespace Game.Maps } } - GridMap gmap = GetGridMap(terrainMapId, x, y); - if (gmap != null) + if (useGridLiquid) { - var map_data = new LiquidData(); - ZLiquidStatus map_result = gmap.GetLiquidStatus(x, y, z, reqLiquidType, map_data, collisionHeight); - // Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER: - if (map_result != ZLiquidStatus.NoWater && (map_data.level > ground_level)) + GridMap gmap = GetGridMap(terrainMapId, x, y); + if (gmap != null) { - // hardcoded in client like this - if (GetId() == 530 && map_data.entry == 2) - map_data.entry = 15; + var map_data = new LiquidData(); + ZLiquidStatus map_result = gmap.GetLiquidStatus(x, y, z, reqLiquidType, map_data, collisionHeight); + // Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER: + if (map_result != ZLiquidStatus.NoWater && (map_data.level > ground_level)) + { + // hardcoded in client like this + if (GetId() == 530 && map_data.entry == 2) + map_data.entry = 15; - data = map_data; + data = map_data; - return map_result; + return map_result; + } } } return result; @@ -2045,66 +2002,92 @@ namespace Game.Maps public void GetFullTerrainStatusForPosition(PhaseShift phaseShift, float x, float y, float z, PositionFullTerrainStatus data, LiquidHeaderTypeFlags reqLiquidType, float collisionHeight = MapConst.DefaultCollesionHeight) { uint terrainMapId = PhasingHandler.GetTerrainMapId(phaseShift, this, x, y); - AreaAndLiquidData vmapData = Global.VMapMgr.GetAreaAndLiquidData(terrainMapId, x, y, z, (byte)reqLiquidType); - if (vmapData.areaInfo.HasValue) - data.areaInfo.Set(new PositionFullTerrainStatus.AreaInfo(vmapData.areaInfo.Value.AdtId, vmapData.areaInfo.Value.RootId, vmapData.areaInfo.Value.GroupId, vmapData.areaInfo.Value.MogpFlags)); - - float mapHeight = MapConst.InvalidHeight; GridMap gmap = GetGridMap(terrainMapId, x, y); + AreaAndLiquidData vmapData = Global.VMapMgr.GetAreaAndLiquidData(terrainMapId, x, y, z, (byte)reqLiquidType); + AreaAndLiquidData dynData = _dynamicTree.GetAreaAndLiquidData(x, y, z, phaseShift, (byte)reqLiquidType); + + uint gridAreaId = 0; + float gridMapHeight = MapConst.InvalidHeight; if (gmap != null) - mapHeight = gmap.GetHeight(x, y); - - // area lookup - AreaTableRecord areaEntry = null; - WMOAreaTableRecord wmoEntry = null; - - // the vmap floor is our floor if either - // - the vmap floor is above the gridmap floor - // or - // - we are below the gridmap floor - if (vmapData.areaInfo.HasValue && (MathFunctions.fuzzyLt(z, mapHeight - MapConst.GroundHeightTolerance) || mapHeight <= vmapData.floorZ)) { - wmoEntry = Global.DB2Mgr.GetWMOAreaTable(vmapData.areaInfo.Value.RootId, vmapData.areaInfo.Value.AdtId, vmapData.areaInfo.Value.GroupId); - if (wmoEntry != null) - areaEntry = CliDB.AreaTableStorage.LookupByKey(wmoEntry.AreaTableID); + gridAreaId = gmap.GetArea(x, y); + gridMapHeight = gmap.GetHeight(x, y); } - if (areaEntry != null) + bool useGridLiquid = true; + AreaAndLiquidData wmoData = null; + + // floor is the height we are closer to (but only if above) + data.FloorZ = MapConst.InvalidHeight; + if (gridMapHeight > MapConst.InvalidHeight && MathFunctions.fuzzyGe(z, gridMapHeight - MapConst.GroundHeightTolerance)) + data.FloorZ = gridMapHeight; + if (vmapData.floorZ > MapConst.InvalidHeight && + MathFunctions.fuzzyGe(z, vmapData.floorZ - MapConst.GroundHeightTolerance) && + (MathFunctions.fuzzyLt(z, gridMapHeight - MapConst.GroundHeightTolerance) || vmapData.floorZ > gridMapHeight)) { - data.AreaId = areaEntry.Id; data.FloorZ = vmapData.floorZ; + wmoData = vmapData; + } + + // NOTE: Objects will not detect a case when a wmo providing area/liquid despawns from under them + // but this is fine as these kind of objects are not meant to be spawned and despawned a lot + // example: Lich King platform + if (dynData.floorZ > MapConst.InvalidHeight && + MathFunctions.fuzzyGe(z, dynData.floorZ - MapConst.GroundHeightTolerance) && + (MathFunctions.fuzzyLt(z, gridMapHeight - MapConst.GroundHeightTolerance) || dynData.floorZ > gridMapHeight) && + (MathFunctions.fuzzyLt(z, vmapData.floorZ - MapConst.GroundHeightTolerance) || dynData.floorZ > vmapData.floorZ)) + { + data.FloorZ = dynData.floorZ; + wmoData = dynData; + } + + if (wmoData != null) + { + if (wmoData.areaInfo.HasValue) + { + data.areaInfo.Set(new PositionFullTerrainStatus.AreaInfo(wmoData.areaInfo.Value.AdtId, wmoData.areaInfo.Value.RootId, wmoData.areaInfo.Value.GroupId, wmoData.areaInfo.Value.MogpFlags)); + // wmo found + var wmoEntry = Global.DB2Mgr.GetWMOAreaTable(wmoData.areaInfo.Value.RootId, wmoData.areaInfo.Value.AdtId, wmoData.areaInfo.Value.GroupId); + data.outdoors = (wmoData.areaInfo.Value.MogpFlags & 0x8) != 0; + if (wmoEntry != null) + { + data.AreaId = wmoEntry.AreaTableID; + if ((wmoEntry.Flags & 4) != 0) + data.outdoors = true; + else if ((wmoEntry.Flags & 2) != 0) + data.outdoors = false; + } + + if (data.AreaId == 0) + data.AreaId = gridAreaId; + + useGridLiquid = !IsInWMOInterior(wmoData.areaInfo.Value.MogpFlags); + } } else { - if (gmap != null) - data.AreaId = gmap.GetArea(x, y); - else - data.AreaId = 0; - - if (data.AreaId == 0) - data.AreaId = i_mapRecord.AreaTableID; - - if (data.AreaId != 0) - areaEntry = CliDB.AreaTableStorage.LookupByKey(data.AreaId); - - data.FloorZ = mapHeight; + data.outdoors = true; + data.AreaId = gridAreaId; + var areaEntry1 = CliDB.AreaTableStorage.LookupByKey(data.AreaId); + if (areaEntry1 != null) + data.outdoors = (areaEntry1.Flags & (AreaFlags.Inside | AreaFlags.Outside)) != AreaFlags.Inside; } - if (vmapData.areaInfo.HasValue) - data.outdoors = IsOutdoorWMO(vmapData.areaInfo.Value.MogpFlags, wmoEntry, areaEntry); - else - data.outdoors = true; // @todo default true taken from old GetAreaId check, maybe review + if (data.AreaId == 0) + data.AreaId = i_mapRecord.AreaTableID; + + var areaEntry = CliDB.AreaTableStorage.LookupByKey(data.AreaId); // liquid processing data.LiquidStatus = ZLiquidStatus.NoWater; - if (vmapData.liquidInfo.HasValue && vmapData.liquidInfo.Value.Level > vmapData.floorZ && z > vmapData.floorZ) + if (wmoData != null && wmoData.liquidInfo.HasValue && wmoData.liquidInfo.Value.Level > wmoData.floorZ) { - uint liquidType = vmapData.liquidInfo.Value.LiquidType; + uint liquidType = wmoData.liquidInfo.Value.LiquidType; if (GetId() == 530 && liquidType == 2) // gotta love blizzard hacks liquidType = 15; uint liquidFlagType = 0; - LiquidTypeRecord liquidData = CliDB.LiquidTypeStorage.LookupByKey(liquidType); + var liquidData = CliDB.LiquidTypeStorage.LookupByKey(liquidType); if (liquidData != null) liquidFlagType = liquidData.SoundBank; @@ -2113,12 +2096,12 @@ namespace Game.Maps uint overrideLiquid = areaEntry.LiquidTypeID[liquidFlagType]; if (overrideLiquid == 0 && areaEntry.ParentAreaID != 0) { - AreaTableRecord zoneEntry = CliDB.AreaTableStorage.LookupByKey(areaEntry.ParentAreaID); + var zoneEntry = CliDB.AreaTableStorage.LookupByKey(areaEntry.ParentAreaID); if (zoneEntry != null) overrideLiquid = zoneEntry.LiquidTypeID[liquidFlagType]; } - LiquidTypeRecord overrideData = CliDB.LiquidTypeStorage.LookupByKey(overrideLiquid); + var overrideData = CliDB.LiquidTypeStorage.LookupByKey(overrideLiquid); if (overrideData != null) { liquidType = overrideLiquid; @@ -2126,14 +2109,13 @@ namespace Game.Maps } } - var liquidInfo = new LiquidData(); - liquidInfo.level = vmapData.liquidInfo.Value.Level; - liquidInfo.depth_level = vmapData.floorZ; - liquidInfo.entry = liquidType; - liquidInfo.type_flags = (LiquidHeaderTypeFlags)(1u << (int)liquidFlagType); - data.LiquidInfo.Set(liquidInfo); + data.LiquidInfo.HasValue = true; + data.LiquidInfo.Value.level = wmoData.liquidInfo.Value.Level; + data.LiquidInfo.Value.depth_level = wmoData.floorZ; + data.LiquidInfo.Value.entry = liquidType; + data.LiquidInfo.Value.type_flags = (LiquidHeaderTypeFlags)(1 << (int)liquidFlagType); - float delta = vmapData.liquidInfo.Value.Level - z; + float delta = wmoData.liquidInfo.Value.Level - z; if (delta > collisionHeight) data.LiquidStatus = ZLiquidStatus.UnderWater; else if (delta > 0.0f) @@ -2144,11 +2126,11 @@ namespace Game.Maps data.LiquidStatus = ZLiquidStatus.AboveWater; } // look up liquid data from grid map - if (gmap != null && (data.LiquidStatus == ZLiquidStatus.AboveWater || data.LiquidStatus == ZLiquidStatus.NoWater)) + if (gmap != null && useGridLiquid) { LiquidData gridMapLiquid = new(); ZLiquidStatus gridMapStatus = gmap.GetLiquidStatus(x, y, z, reqLiquidType, gridMapLiquid, collisionHeight); - if (gridMapStatus != ZLiquidStatus.NoWater && (gridMapLiquid.level > vmapData.floorZ)) + if (gridMapStatus != ZLiquidStatus.NoWater && (wmoData == null || gridMapLiquid.level > wmoData.floorZ)) { if (GetId() == 530 && gridMapLiquid.entry == 2) gridMapLiquid.entry = 15; @@ -2629,7 +2611,7 @@ namespace Game.Maps else // value changed, update heap position { Cypher.Assert(now < next.respawnTime); // infinite loop guard - //_respawnTimes.decrease(next->handle); + //_respawnTimes.decrease(next.handle); } } } diff --git a/Source/Game/Movement/Generators/PathGenerator.cs b/Source/Game/Movement/Generators/PathGenerator.cs index ad21b85bf..24df1f0dd 100644 --- a/Source/Game/Movement/Generators/PathGenerator.cs +++ b/Source/Game/Movement/Generators/PathGenerator.cs @@ -774,7 +774,10 @@ namespace Game.Movement void NormalizePath() { for (uint i = 0; i < _pathPoints.Length; ++i) + { + _pathPoints[i].Z += _sourceUnit.GetCollisionHeight(); _sourceUnit.UpdateAllowedPositionZ(_pathPoints[i].X, _pathPoints[i].Y, ref _pathPoints[i].Z); + } } void BuildShortcut() diff --git a/Source/Scripts/Spells/Druid.cs b/Source/Scripts/Spells/Druid.cs index 2ed2e5076..0773cab11 100644 --- a/Source/Scripts/Spells/Druid.cs +++ b/Source/Scripts/Spells/Druid.cs @@ -1449,7 +1449,7 @@ namespace Scripts.Spells.Druid { SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell_id, difficulty); - if (requireOutdoors && !targetPlayer.GetMap().IsOutdoors(targetPlayer.GetPhaseShift(), targetPlayer.GetPositionX(), targetPlayer.GetPositionY(), targetPlayer.GetPositionZ())) + if (requireOutdoors && !targetPlayer.IsOutdoors()) return SpellCastResult.OnlyOutdoors; return spellInfo.CheckLocation(targetPlayer.GetMapId(), targetPlayer.GetZoneId(), targetPlayer.GetAreaId(), targetPlayer);