Core/Entities: Fix some weird movement due to los issues
Port From (https://github.com/TrinityCore/TrinityCore/commit/446fc391f02d3ca8e1332b58f910a5b7d1c8e226)
This commit is contained in:
@@ -62,6 +62,8 @@ namespace Framework.Constants
|
|||||||
public const float InvalidHeight = -100000.0f;
|
public const float InvalidHeight = -100000.0f;
|
||||||
public const float MaxHeight = 100000.0f;
|
public const float MaxHeight = 100000.0f;
|
||||||
public const float MaxFallDistance = 250000.0f;
|
public const float MaxFallDistance = 250000.0f;
|
||||||
|
public const float GroundHeightTolerance = 0.05f;
|
||||||
|
public const float DefaultCollesionHeight = 2.03128f; // Most common value in dbc
|
||||||
|
|
||||||
public const uint MapMagic = 0x5350414D; //"MAPS";
|
public const uint MapMagic = 0x5350414D; //"MAPS";
|
||||||
public const uint MapVersionMagic = 0x392E3176; //"v1.9";
|
public const uint MapVersionMagic = 0x392E3176; //"v1.9";
|
||||||
|
|||||||
@@ -902,8 +902,8 @@ namespace Game.Chat
|
|||||||
Global.DB2Mgr.Map2ZoneCoordinates((int)zoneId, ref zoneX, ref zoneY);
|
Global.DB2Mgr.Map2ZoneCoordinates((int)zoneId, ref zoneX, ref zoneY);
|
||||||
|
|
||||||
Map map = obj.GetMap();
|
Map map = obj.GetMap();
|
||||||
float groundZ = map.GetHeight(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), MapConst.MaxHeight);
|
float groundZ = obj.GetMapHeight(obj.GetPositionX(), obj.GetPositionY(), MapConst.MaxHeight);
|
||||||
float floorZ = map.GetHeight(obj.GetPhaseShift(), obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ());
|
float floorZ = obj.GetMapHeight(obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ());
|
||||||
|
|
||||||
GridCoord gridCoord = GridDefines.ComputeGridCoord(obj.GetPositionX(), obj.GetPositionY());
|
GridCoord gridCoord = GridDefines.ComputeGridCoord(obj.GetPositionX(), obj.GetPositionY());
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ namespace Game.Collision
|
|||||||
|
|
||||||
public float GetHeight(float x, float y, float z, float maxSearchDist, PhaseShift phaseShift)
|
public float GetHeight(float x, float y, float z, float maxSearchDist, PhaseShift phaseShift)
|
||||||
{
|
{
|
||||||
Vector3 v = new(x, y, z + 0.5f);
|
Vector3 v = new(x, y, z);
|
||||||
Ray r = new(v, new Vector3(0, 0, -1));
|
Ray r = new(v, new Vector3(0, 0, -1));
|
||||||
DynamicTreeIntersectionCallback callback = new(phaseShift);
|
DynamicTreeIntersectionCallback callback = new(phaseShift);
|
||||||
impl.IntersectZAllignedRay(r, callback, ref maxSearchDist);
|
impl.IntersectZAllignedRay(r, callback, ref maxSearchDist);
|
||||||
|
|||||||
@@ -2717,7 +2717,7 @@ namespace Game.Entities
|
|||||||
// Set the movement flags if the creature is in that mode. (Only fly if actually in air, only swim if in water, etc)
|
// Set the movement flags if the creature is in that mode. (Only fly if actually in air, only swim if in water, etc)
|
||||||
float ground = GetFloorZ();
|
float ground = GetFloorZ();
|
||||||
|
|
||||||
bool isInAir = (MathFunctions.fuzzyGt(GetPositionZMinusOffset(), ground + 0.05f) || MathFunctions.fuzzyLt(GetPositionZMinusOffset(), ground - 0.05f)); // Can be underground too, prevent the falling
|
bool isInAir = (MathFunctions.fuzzyGt(GetPositionZMinusOffset(), ground + MapConst.GroundHeightTolerance) || MathFunctions.fuzzyLt(GetPositionZMinusOffset(), ground - MapConst.GroundHeightTolerance)); // Can be underground too, prevent the falling
|
||||||
|
|
||||||
if (GetCreatureTemplate().InhabitType.HasAnyFlag(InhabitType.Air) && isInAir && !IsFalling())
|
if (GetCreatureTemplate().InhabitType.HasAnyFlag(InhabitType.Air) && isInAir && !IsFalling())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1917,11 +1917,12 @@ namespace Game.Entities
|
|||||||
{
|
{
|
||||||
if (IsInWorld)
|
if (IsInWorld)
|
||||||
{
|
{
|
||||||
|
oz += GetCollisionHeight();
|
||||||
float x, y, z;
|
float x, y, z;
|
||||||
if (IsTypeId(TypeId.Player))
|
if (IsTypeId(TypeId.Player))
|
||||||
{
|
{
|
||||||
GetPosition(out x, out y, out z);
|
GetPosition(out x, out y, out z);
|
||||||
z += GetMidsectionHeight();
|
z += GetCollisionHeight();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
GetHitSpherePointFor(new Position(ox, oy, oz), out x, out y, out z);
|
GetHitSpherePointFor(new Position(ox, oy, oz), out x, out y, out z);
|
||||||
@@ -1937,21 +1938,30 @@ namespace Game.Entities
|
|||||||
if (!IsInMap(obj))
|
if (!IsInMap(obj))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
float x, y, z;
|
float ox, oy, oz;
|
||||||
if (obj.IsTypeId(TypeId.Player))
|
if (obj.IsTypeId(TypeId.Player))
|
||||||
{
|
{
|
||||||
obj.GetPosition(out x, out y, out z);
|
obj.GetPosition(out ox, out oy, out oz);
|
||||||
z += GetMidsectionHeight();
|
oz += GetCollisionHeight();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
obj.GetHitSpherePointFor(new (GetPositionX(), GetPositionY(), GetPositionZ() + GetMidsectionHeight()), out x, out y, out z);
|
obj.GetHitSpherePointFor(new (GetPositionX(), GetPositionY(), GetPositionZ() + GetCollisionHeight()), out ox, out oy, out oz);
|
||||||
|
|
||||||
return IsWithinLOS(x, y, z, checks, ignoreFlags);
|
float x, y, z;
|
||||||
|
if (IsPlayer())
|
||||||
|
{
|
||||||
|
GetPosition(out x, out y, out z);
|
||||||
|
z += GetCollisionHeight();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
GetHitSpherePointFor(new (obj.GetPositionX(), obj.GetPositionY(), obj.GetPositionZ() + obj.GetCollisionHeight()), out x, out y, out z);
|
||||||
|
|
||||||
|
return GetMap().IsInLineOfSight(GetPhaseShift(), x, y, z, ox, oy, oz, checks, ignoreFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
Position GetHitSpherePointFor(Position dest)
|
Position GetHitSpherePointFor(Position dest)
|
||||||
{
|
{
|
||||||
Vector3 vThis = new(GetPositionX(), GetPositionY(), GetPositionZ() + GetMidsectionHeight());
|
Vector3 vThis = new(GetPositionX(), GetPositionY(), GetPositionZ() + GetCollisionHeight());
|
||||||
Vector3 vObj = new(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
Vector3 vObj = new(dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ());
|
||||||
Vector3 contactPoint = vThis + (vObj - vThis).directionOrZero() * Math.Min(dest.GetExactDist(GetPosition()), GetCombatReach());
|
Vector3 contactPoint = vThis + (vObj - vThis).directionOrZero() * Math.Min(dest.GetExactDist(GetPosition()), GetCombatReach());
|
||||||
|
|
||||||
@@ -2076,9 +2086,7 @@ namespace Game.Entities
|
|||||||
|
|
||||||
public void UpdateGroundPositionZ(float x, float y, ref float z)
|
public void UpdateGroundPositionZ(float x, float y, ref float z)
|
||||||
{
|
{
|
||||||
float new_z = GetMap().GetHeight(GetPhaseShift(), x, y, z, true);
|
z = GetMapHeight(x, y, z);
|
||||||
if (new_z > MapConst.InvalidHeight)
|
|
||||||
z = new_z + 0.05f; // just to be sure that we are not a few pixel under the surface
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateAllowedPositionZ(float x, float y, ref float z)
|
public void UpdateAllowedPositionZ(float x, float y, ref float z)
|
||||||
@@ -2098,8 +2106,8 @@ namespace Game.Entities
|
|||||||
bool canSwim = ToCreature().CanSwim();
|
bool canSwim = ToCreature().CanSwim();
|
||||||
float ground_z = z;
|
float ground_z = z;
|
||||||
float max_z = canSwim
|
float max_z = canSwim
|
||||||
? GetMap().GetWaterOrGroundLevel(GetPhaseShift(), x, y, z, ref ground_z, !ToUnit().HasAuraType(AuraType.WaterWalk))
|
? GetMapWaterOrGroundLevel(x, y, z, ref ground_z)
|
||||||
: ((ground_z = GetMap().GetHeight(GetPhaseShift(), x, y, z, true)));
|
: (ground_z = GetMapHeight(x, y, z));
|
||||||
if (max_z > MapConst.InvalidHeight)
|
if (max_z > MapConst.InvalidHeight)
|
||||||
{
|
{
|
||||||
if (z > max_z)
|
if (z > max_z)
|
||||||
@@ -2110,7 +2118,7 @@ namespace Game.Entities
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float ground_z = GetMap().GetHeight(GetPhaseShift(), x, y, z, true);
|
float ground_z = GetMapHeight(x, y, z);
|
||||||
if (z < ground_z)
|
if (z < ground_z)
|
||||||
z = ground_z;
|
z = ground_z;
|
||||||
}
|
}
|
||||||
@@ -2122,7 +2130,7 @@ namespace Game.Entities
|
|||||||
if (!ToPlayer().CanFly())
|
if (!ToPlayer().CanFly())
|
||||||
{
|
{
|
||||||
float ground_z = z;
|
float ground_z = z;
|
||||||
float max_z = GetMap().GetWaterOrGroundLevel(GetPhaseShift(), x, y, z, ref ground_z, !ToUnit().HasAuraType(AuraType.WaterWalk));
|
float max_z = GetMapWaterOrGroundLevel(x, y, z, ref ground_z);
|
||||||
if (max_z > MapConst.InvalidHeight)
|
if (max_z > MapConst.InvalidHeight)
|
||||||
{
|
{
|
||||||
if (z > max_z)
|
if (z > max_z)
|
||||||
@@ -2133,7 +2141,7 @@ namespace Game.Entities
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float ground_z = GetMap().GetHeight(GetPhaseShift(), x, y, z, true);
|
float ground_z = GetMapHeight(x, y, z);
|
||||||
if (z < ground_z)
|
if (z < ground_z)
|
||||||
z = ground_z;
|
z = ground_z;
|
||||||
}
|
}
|
||||||
@@ -2141,7 +2149,7 @@ namespace Game.Entities
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
float ground_z = GetMap().GetHeight(GetPhaseShift(), x, y, z, true);
|
float ground_z = GetMapHeight(x, y, z);
|
||||||
if (ground_z > MapConst.InvalidHeight)
|
if (ground_z > MapConst.InvalidHeight)
|
||||||
z = ground_z;
|
z = ground_z;
|
||||||
break;
|
break;
|
||||||
@@ -2202,7 +2210,7 @@ namespace Game.Entities
|
|||||||
public Position GetNearPosition(float dist, float angle)
|
public Position GetNearPosition(float dist, float angle)
|
||||||
{
|
{
|
||||||
var pos = GetPosition();
|
var pos = GetPosition();
|
||||||
MovePosition(ref pos, dist, angle);
|
MovePosition(pos, dist, angle);
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2216,7 +2224,7 @@ namespace Game.Entities
|
|||||||
public Position GetRandomNearPosition(float radius)
|
public Position GetRandomNearPosition(float radius)
|
||||||
{
|
{
|
||||||
var pos = GetPosition();
|
var pos = GetPosition();
|
||||||
MovePosition(ref pos, radius * (float)RandomHelper.NextDouble(), (float)RandomHelper.NextDouble() * MathFunctions.PI * 2);
|
MovePosition(pos, radius * (float)RandomHelper.NextDouble(), (float)RandomHelper.NextDouble() * MathFunctions.PI * 2);
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2226,7 +2234,7 @@ namespace Game.Entities
|
|||||||
GetNearPoint(obj, out x, out y, out z, obj.GetCombatReach(), distance2d, GetAngle(obj));
|
GetNearPoint(obj, out x, out y, out z, obj.GetCombatReach(), distance2d, GetAngle(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MovePosition(ref Position pos, float dist, float angle)
|
public void MovePosition(Position pos, float dist, float angle)
|
||||||
{
|
{
|
||||||
angle += GetOrientation();
|
angle += GetOrientation();
|
||||||
float destx = pos.posX + dist * (float)Math.Cos(angle);
|
float destx = pos.posX + dist * (float)Math.Cos(angle);
|
||||||
@@ -2239,8 +2247,8 @@ namespace Game.Entities
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float ground = GetMap().GetHeight(GetPhaseShift(), destx, desty, MapConst.MaxHeight, true);
|
float ground = GetMapHeight(destx, desty, MapConst.MaxHeight);
|
||||||
float floor = GetMap().GetHeight(GetPhaseShift(), destx, desty, pos.posZ, true);
|
float floor = GetMapHeight(destx, desty, pos.posZ);
|
||||||
float destz = Math.Abs(ground - pos.posZ) <= Math.Abs(floor - pos.posZ) ? ground : floor;
|
float destz = Math.Abs(ground - pos.posZ) <= Math.Abs(floor - pos.posZ) ? ground : floor;
|
||||||
|
|
||||||
float step = dist / 10.0f;
|
float step = dist / 10.0f;
|
||||||
@@ -2270,37 +2278,12 @@ namespace Game.Entities
|
|||||||
pos.SetOrientation(GetOrientation());
|
pos.SetOrientation(GetOrientation());
|
||||||
}
|
}
|
||||||
|
|
||||||
float NormalizeZforCollision(WorldObject obj, float x, float y, float z)
|
|
||||||
{
|
|
||||||
float ground = obj.GetMap().GetHeight(obj.GetPhaseShift(), x, y, MapConst.MaxHeight, true);
|
|
||||||
float floor = obj.GetMap().GetHeight(obj.GetPhaseShift(), x, y, z + 2.0f, true);
|
|
||||||
float helper = Math.Abs(ground - z) <= Math.Abs(floor - z) ? ground : floor;
|
|
||||||
if (z > helper) // must be above ground
|
|
||||||
{
|
|
||||||
Unit unit = obj.ToUnit();
|
|
||||||
if (unit)
|
|
||||||
{
|
|
||||||
if (unit.CanFly())
|
|
||||||
return z;
|
|
||||||
}
|
|
||||||
LiquidData liquid_status;
|
|
||||||
ZLiquidStatus res = obj.GetMap().GetLiquidStatus(obj.GetPhaseShift(), x, y, z, MapConst.MapAllLiquidTypes, out liquid_status);
|
|
||||||
if (res != 0 && liquid_status.level > helper) // water must be above ground
|
|
||||||
{
|
|
||||||
if (liquid_status.level > z) // z is underwater
|
|
||||||
return z;
|
|
||||||
else
|
|
||||||
return Math.Abs(liquid_status.level - z) <= Math.Abs(helper - z) ? liquid_status.level : helper;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return helper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MovePositionToFirstCollision(ref Position pos, float dist, float angle)
|
public void MovePositionToFirstCollision(ref Position pos, float dist, float angle)
|
||||||
{
|
{
|
||||||
angle += GetOrientation();
|
angle += GetOrientation();
|
||||||
float destx = pos.posX + dist * (float)Math.Cos(angle);
|
float destx = pos.posX + dist * (float)Math.Cos(angle);
|
||||||
float desty = pos.posY + dist * (float)Math.Sin(angle);
|
float desty = pos.posY + dist * (float)Math.Sin(angle);
|
||||||
|
float destz = pos.posZ;
|
||||||
|
|
||||||
// Prevent invalid coordinates here, position is unchanged
|
// Prevent invalid coordinates here, position is unchanged
|
||||||
if (!GridDefines.IsValidMapCoord(destx, desty))
|
if (!GridDefines.IsValidMapCoord(destx, desty))
|
||||||
@@ -2309,8 +2292,8 @@ namespace Game.Entities
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float destz = NormalizeZforCollision(this, destx, desty, pos.GetPositionZ());
|
UpdateAllowedPositionZ(destx, desty, ref destz);
|
||||||
bool col = Global.VMapMgr.GetObjectHitPos(PhasingHandler.GetTerrainMapId(GetPhaseShift(), GetMap(), pos.posX, pos.posY), pos.posX, pos.posY, pos.posZ + 0.5f, destx, desty, destz + 0.5f, 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, destx, desty, destz, out destx, out desty, out destz, -0.5f);
|
||||||
|
|
||||||
// collision occured
|
// collision occured
|
||||||
if (col)
|
if (col)
|
||||||
@@ -2322,7 +2305,7 @@ namespace Game.Entities
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check dynamic collision
|
// check dynamic collision
|
||||||
col = GetMap().GetObjectHitPos(GetPhaseShift(), pos.posX, pos.posY, pos.posZ + 0.5f, destx, desty, destz + 0.5f, out destx, out desty, out destz, -0.5f);
|
col = GetMap().GetObjectHitPos(GetPhaseShift(), pos.posX, pos.posY, pos.posZ, destx, desty, destz, out destx, out desty, out destz, -0.5f);
|
||||||
|
|
||||||
// Collided with a gameobject
|
// Collided with a gameobject
|
||||||
if (col)
|
if (col)
|
||||||
@@ -2341,7 +2324,7 @@ namespace Game.Entities
|
|||||||
{
|
{
|
||||||
destx -= step * (float)Math.Cos(angle);
|
destx -= step * (float)Math.Cos(angle);
|
||||||
desty -= step * (float)Math.Sin(angle);
|
desty -= step * (float)Math.Sin(angle);
|
||||||
destz = NormalizeZforCollision(this, destx, desty, pos.GetPositionZ());
|
UpdateAllowedPositionZ(destx, desty, ref destz);
|
||||||
}
|
}
|
||||||
// we have correct destz now
|
// we have correct destz now
|
||||||
else
|
else
|
||||||
@@ -2353,7 +2336,7 @@ namespace Game.Entities
|
|||||||
|
|
||||||
GridDefines.NormalizeMapCoord(ref pos.posX);
|
GridDefines.NormalizeMapCoord(ref pos.posX);
|
||||||
GridDefines.NormalizeMapCoord(ref pos.posY);
|
GridDefines.NormalizeMapCoord(ref pos.posY);
|
||||||
pos.posZ = NormalizeZforCollision(this, destx, desty, pos.GetPositionZ());
|
UpdateAllowedPositionZ(destx, desty, ref pos.posZ);
|
||||||
pos.SetOrientation(GetOrientation());
|
pos.SetOrientation(GetOrientation());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2361,7 +2344,24 @@ namespace Game.Entities
|
|||||||
{
|
{
|
||||||
if (!IsInWorld)
|
if (!IsInWorld)
|
||||||
return m_staticFloorZ;
|
return m_staticFloorZ;
|
||||||
return Math.Max(m_staticFloorZ, GetMap().GetGameObjectFloor(GetPhaseShift(), GetPositionX(), GetPositionY(), GetPositionZ()));
|
return Math.Max(m_staticFloorZ, GetMap().GetGameObjectFloor(GetPhaseShift(), GetPositionX(), GetPositionY(), GetPositionZ() + GetCollisionHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetMapHeight(float x, float y, float z, bool vmap = true, float distanceToSearch = MapConst.DefaultHeightSearch)
|
||||||
|
{
|
||||||
|
if (z != MapConst.MaxHeight)
|
||||||
|
z += GetCollisionHeight();
|
||||||
|
|
||||||
|
return GetMap().GetHeight(GetPhaseShift(), x, y, z, vmap, distanceToSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLocationInstanceId(uint _instanceId) { instanceId = _instanceId; }
|
public void SetLocationInstanceId(uint _instanceId) { instanceId = _instanceId; }
|
||||||
|
|||||||
@@ -824,7 +824,8 @@ namespace Game.Entities
|
|||||||
{
|
{
|
||||||
var displayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetNativeDisplayId());
|
var displayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetNativeDisplayId());
|
||||||
var modelData = CliDB.CreatureModelDataStorage.LookupByKey(displayInfo.ModelID);
|
var modelData = CliDB.CreatureModelDataStorage.LookupByKey(displayInfo.ModelID);
|
||||||
return scaleMod * (mountModelData.MountHeight + modelData.CollisionHeight * 0.5f);
|
float collisionHeight = scaleMod * (mountModelData.MountHeight + modelData.CollisionHeight * modelData.ModelScale * displayInfo.CreatureModelScale * 0.5f);
|
||||||
|
return collisionHeight == 0.0f ? MapConst.DefaultCollesionHeight : collisionHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -833,7 +834,8 @@ namespace Game.Entities
|
|||||||
var defaultDisplayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetNativeDisplayId());
|
var defaultDisplayInfo = CliDB.CreatureDisplayInfoStorage.LookupByKey(GetNativeDisplayId());
|
||||||
var defaultModelData = CliDB.CreatureModelDataStorage.LookupByKey(defaultDisplayInfo.ModelID);
|
var defaultModelData = CliDB.CreatureModelDataStorage.LookupByKey(defaultDisplayInfo.ModelID);
|
||||||
|
|
||||||
return scaleMod * defaultModelData.CollisionHeight;
|
float collisionHeight1 = scaleMod * defaultModelData.CollisionHeight * defaultModelData.ModelScale * defaultDisplayInfo.CreatureModelScale;
|
||||||
|
return collisionHeight1 == 0.0f ? MapConst.DefaultCollesionHeight : collisionHeight1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guardian GetGuardianPet()
|
public Guardian GetGuardianPet()
|
||||||
@@ -1005,12 +1007,12 @@ namespace Game.Entities
|
|||||||
if (player != null)
|
if (player != null)
|
||||||
player.SetFallInformation(0, GetPositionZ());
|
player.SetFallInformation(0, GetPositionZ());
|
||||||
|
|
||||||
float height = pos.GetPositionZ();
|
float height = pos.GetPositionZ() + vehicle.GetBase().GetCollisionHeight();
|
||||||
|
|
||||||
MoveSplineInit init = new(this);
|
MoveSplineInit init = new(this);
|
||||||
|
|
||||||
// Creatures without inhabit type air should begin falling after exiting the vehicle
|
// Creatures without inhabit type air should begin falling after exiting the vehicle
|
||||||
if (IsTypeId(TypeId.Unit) && !ToCreature().CanFly() && height > GetMap().GetWaterOrGroundLevel(GetPhaseShift(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), ref height) + 0.1f)
|
if (IsTypeId(TypeId.Unit) && !ToCreature().CanFly() && height > GetMap().GetWaterOrGroundLevel(GetPhaseShift(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ() + vehicle.GetBase().GetCollisionHeight(), ref height))
|
||||||
init.SetFall();
|
init.SetFall();
|
||||||
|
|
||||||
init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), height, false);
|
init.MoveTo(pos.GetPositionX(), pos.GetPositionY(), height, false);
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ namespace Game.Maps
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get water state on map
|
// Get water state on map
|
||||||
public ZLiquidStatus GetLiquidStatus(float x, float y, float z, uint ReqLiquidType, LiquidData data)
|
public ZLiquidStatus GetLiquidStatus(float x, float y, float z, uint ReqLiquidType, LiquidData data, float collisionHeight)
|
||||||
{
|
{
|
||||||
// Check water type (if no water return)
|
// Check water type (if no water return)
|
||||||
if (_liquidGlobalFlags == 0 && _liquidFlags == null)
|
if (_liquidGlobalFlags == 0 && _liquidFlags == null)
|
||||||
@@ -568,7 +568,7 @@ namespace Game.Maps
|
|||||||
float ground_level = GetHeight(x, y);
|
float ground_level = GetHeight(x, y);
|
||||||
|
|
||||||
// Check water level and ground level
|
// Check water level and ground level
|
||||||
if (liquid_level < ground_level || z < ground_level - 2)
|
if (liquid_level < ground_level || z < ground_level)
|
||||||
return ZLiquidStatus.NoWater;
|
return ZLiquidStatus.NoWater;
|
||||||
|
|
||||||
// All ok in water . store data
|
// All ok in water . store data
|
||||||
@@ -583,7 +583,7 @@ namespace Game.Maps
|
|||||||
// For speed check as int values
|
// For speed check as int values
|
||||||
float delta = liquid_level - z;
|
float delta = liquid_level - z;
|
||||||
|
|
||||||
if (delta > 2.0f) // Under water
|
if (delta > collisionHeight) // Under water
|
||||||
return ZLiquidStatus.UnderWater;
|
return ZLiquidStatus.UnderWater;
|
||||||
if (delta > 0.0f) // In water
|
if (delta > 0.0f) // In water
|
||||||
return ZLiquidStatus.InWater;
|
return ZLiquidStatus.InWater;
|
||||||
|
|||||||
+17
-21
@@ -1694,23 +1694,23 @@ namespace Game.Maps
|
|||||||
return childMap != null && childMap.i_gridFileExists[(int)(gx * MapConst.MaxGrids + gy)];
|
return childMap != null && childMap.i_gridFileExists[(int)(gx * MapConst.MaxGrids + gy)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetWaterOrGroundLevel(PhaseShift phaseShift, float x, float y, float z)
|
public float GetWaterOrGroundLevel(PhaseShift phaseShift, float x, float y, float z, float collisionHeight = MapConst.DefaultCollesionHeight)
|
||||||
{
|
{
|
||||||
float ground = 0f;
|
float ground = 0f;
|
||||||
return GetWaterOrGroundLevel(phaseShift, x, y, z, ref ground);
|
return GetWaterOrGroundLevel(phaseShift, x, y, z, ref ground);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetWaterOrGroundLevel(PhaseShift phaseShift, float x, float y, float z, ref float ground, bool swim = false)
|
public float GetWaterOrGroundLevel(PhaseShift phaseShift, float x, float y, float z, ref float ground, bool swim = false, float collisionHeight = MapConst.DefaultCollesionHeight)
|
||||||
{
|
{
|
||||||
if (GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y) != null)
|
if (GetGridMap(PhasingHandler.GetTerrainMapId(phaseShift, this, x, y), x, y) != null)
|
||||||
{
|
{
|
||||||
// we need ground level (including grid height version) for proper return water level in point
|
// we need ground level (including grid height version) for proper return water level in point
|
||||||
float ground_z = GetHeight(phaseShift, x, y, z, true, 50.0f);
|
float ground_z = GetHeight(phaseShift, x, y, z + collisionHeight, true, 50.0f);
|
||||||
ground = ground_z;
|
ground = ground_z;
|
||||||
|
|
||||||
LiquidData liquid_status;
|
LiquidData liquid_status;
|
||||||
|
|
||||||
ZLiquidStatus res = GetLiquidStatus(phaseShift, x, y, ground_z, MapConst.MapAllLiquidTypes, out liquid_status);
|
ZLiquidStatus res = GetLiquidStatus(phaseShift, x, y, ground_z, MapConst.MapAllLiquidTypes, out liquid_status, collisionHeight);
|
||||||
switch (res)
|
switch (res)
|
||||||
{
|
{
|
||||||
case ZLiquidStatus.AboveWater:
|
case ZLiquidStatus.AboveWater:
|
||||||
@@ -1734,8 +1734,7 @@ namespace Game.Maps
|
|||||||
if (gmap != null)
|
if (gmap != null)
|
||||||
{
|
{
|
||||||
float gridHeight = gmap.GetHeight(x, y);
|
float gridHeight = gmap.GetHeight(x, y);
|
||||||
// look from a bit higher pos to find the floor, ignore under surface case
|
if (z > gridHeight)
|
||||||
if (z + 2.0f > gridHeight)
|
|
||||||
mapHeight = gridHeight;
|
mapHeight = gridHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1743,8 +1742,7 @@ namespace Game.Maps
|
|||||||
if (checkVMap)
|
if (checkVMap)
|
||||||
{
|
{
|
||||||
if (Global.VMapMgr.IsHeightCalcEnabled())
|
if (Global.VMapMgr.IsHeightCalcEnabled())
|
||||||
vmapHeight = Global.VMapMgr.GetHeight(terrainMapId, x, y, z + 2.0f, maxSearchDist);
|
vmapHeight = Global.VMapMgr.GetHeight(terrainMapId, x, y, z, maxSearchDist);
|
||||||
// look from a bit higher pos to find the floor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// mapHeight set for any above raw ground Z or <= INVALID_HEIGHT
|
// mapHeight set for any above raw ground Z or <= INVALID_HEIGHT
|
||||||
@@ -1887,8 +1885,7 @@ namespace Game.Maps
|
|||||||
if (gmap != null)
|
if (gmap != null)
|
||||||
{
|
{
|
||||||
float mapHeight = gmap.GetHeight(x, y);
|
float mapHeight = gmap.GetHeight(x, y);
|
||||||
// z + 2.0f condition taken from GetHeight(), not sure if it's such a great choice...
|
if (z > mapHeight && mapHeight > check_z)
|
||||||
if (z + 2.0f > mapHeight && mapHeight > check_z)
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -1975,13 +1972,12 @@ namespace Game.Maps
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZLiquidStatus GetLiquidStatus(PhaseShift phaseShift, float x, float y, float z, uint ReqLiquidType)
|
public ZLiquidStatus GetLiquidStatus(PhaseShift phaseShift, float x, float y, float z, uint ReqLiquidType, float collisionHeight = MapConst.DefaultCollesionHeight)
|
||||||
{
|
{
|
||||||
LiquidData throwaway;
|
return GetLiquidStatus(phaseShift, x, y, z, ReqLiquidType, out _, collisionHeight);
|
||||||
return GetLiquidStatus(phaseShift, x, y, z, ReqLiquidType, out throwaway);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZLiquidStatus GetLiquidStatus(PhaseShift phaseShift, float x, float y, float z, uint ReqLiquidType, out LiquidData data)
|
public ZLiquidStatus GetLiquidStatus(PhaseShift phaseShift, float x, float y, float z, uint ReqLiquidType, out LiquidData data, float collisionHeight = MapConst.DefaultCollesionHeight)
|
||||||
{
|
{
|
||||||
data = new LiquidData();
|
data = new LiquidData();
|
||||||
var result = ZLiquidStatus.NoWater;
|
var result = ZLiquidStatus.NoWater;
|
||||||
@@ -2037,7 +2033,7 @@ namespace Game.Maps
|
|||||||
float delta = liquid_level - z;
|
float delta = liquid_level - z;
|
||||||
|
|
||||||
// Get position delta
|
// Get position delta
|
||||||
if (delta > 2.0f) // Under water
|
if (delta > collisionHeight) // Under water
|
||||||
return ZLiquidStatus.UnderWater;
|
return ZLiquidStatus.UnderWater;
|
||||||
if (delta > 0.0f) // In water
|
if (delta > 0.0f) // In water
|
||||||
return ZLiquidStatus.InWater;
|
return ZLiquidStatus.InWater;
|
||||||
@@ -2051,7 +2047,7 @@ namespace Game.Maps
|
|||||||
if (gmap != null)
|
if (gmap != null)
|
||||||
{
|
{
|
||||||
var map_data = new LiquidData();
|
var map_data = new LiquidData();
|
||||||
ZLiquidStatus map_result = gmap.GetLiquidStatus(x, y, z, ReqLiquidType, map_data);
|
ZLiquidStatus map_result = gmap.GetLiquidStatus(x, y, z, ReqLiquidType, map_data, collisionHeight);
|
||||||
// Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER:
|
// Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER:
|
||||||
if (map_result != ZLiquidStatus.NoWater && (map_data.level > ground_level))
|
if (map_result != ZLiquidStatus.NoWater && (map_data.level > ground_level))
|
||||||
{
|
{
|
||||||
@@ -2067,7 +2063,7 @@ namespace Game.Maps
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GetFullTerrainStatusForPosition(PhaseShift phaseShift, float x, float y, float z, PositionFullTerrainStatus data, uint reqLiquidType = MapConst.MapAllLiquidTypes)
|
public void GetFullTerrainStatusForPosition(PhaseShift phaseShift, float x, float y, float z, PositionFullTerrainStatus data, uint reqLiquidType = MapConst.MapAllLiquidTypes, float collisionHeight = MapConst.DefaultCollesionHeight)
|
||||||
{
|
{
|
||||||
uint terrainMapId = PhasingHandler.GetTerrainMapId(phaseShift, this, x, y);
|
uint terrainMapId = PhasingHandler.GetTerrainMapId(phaseShift, this, x, y);
|
||||||
AreaAndLiquidData vmapData = Global.VMapMgr.GetAreaAndLiquidData(terrainMapId, x, y, z, reqLiquidType);
|
AreaAndLiquidData vmapData = Global.VMapMgr.GetAreaAndLiquidData(terrainMapId, x, y, z, reqLiquidType);
|
||||||
@@ -2081,7 +2077,7 @@ namespace Game.Maps
|
|||||||
|
|
||||||
// area lookup
|
// area lookup
|
||||||
AreaTableRecord areaEntry = null;
|
AreaTableRecord areaEntry = null;
|
||||||
if (vmapData.areaInfo.HasValue && (z + 2.0f <= mapHeight || mapHeight <= vmapData.floorZ))
|
if (vmapData.areaInfo.HasValue && (z <= mapHeight || mapHeight <= vmapData.floorZ))
|
||||||
{
|
{
|
||||||
WMOAreaTableRecord wmoEntry = Global.DB2Mgr.GetWMOAreaTable(vmapData.areaInfo.Value.RootId, vmapData.areaInfo.Value.AdtId, vmapData.areaInfo.Value.GroupId);
|
WMOAreaTableRecord wmoEntry = Global.DB2Mgr.GetWMOAreaTable(vmapData.areaInfo.Value.RootId, vmapData.areaInfo.Value.AdtId, vmapData.areaInfo.Value.GroupId);
|
||||||
if (wmoEntry != null)
|
if (wmoEntry != null)
|
||||||
@@ -2110,7 +2106,7 @@ namespace Game.Maps
|
|||||||
|
|
||||||
// liquid processing
|
// liquid processing
|
||||||
data.LiquidStatus = ZLiquidStatus.NoWater;
|
data.LiquidStatus = ZLiquidStatus.NoWater;
|
||||||
if (vmapData.liquidInfo.HasValue && vmapData.liquidInfo.Value.Level > vmapData.floorZ && z + 2.0f > vmapData.floorZ)
|
if (vmapData.liquidInfo.HasValue && vmapData.liquidInfo.Value.Level > vmapData.floorZ && z > vmapData.floorZ)
|
||||||
{
|
{
|
||||||
uint liquidType = vmapData.liquidInfo.Value.LiquidType;
|
uint liquidType = vmapData.liquidInfo.Value.LiquidType;
|
||||||
if (GetId() == 530 && liquidType == 2) // gotta love blizzard hacks
|
if (GetId() == 530 && liquidType == 2) // gotta love blizzard hacks
|
||||||
@@ -2147,7 +2143,7 @@ namespace Game.Maps
|
|||||||
data.LiquidInfo.Set(liquidInfo);
|
data.LiquidInfo.Set(liquidInfo);
|
||||||
|
|
||||||
float delta = vmapData.liquidInfo.Value.Level - z;
|
float delta = vmapData.liquidInfo.Value.Level - z;
|
||||||
if (delta > 2.0f)
|
if (delta > collisionHeight)
|
||||||
data.LiquidStatus = ZLiquidStatus.UnderWater;
|
data.LiquidStatus = ZLiquidStatus.UnderWater;
|
||||||
else if (delta > 0.0f)
|
else if (delta > 0.0f)
|
||||||
data.LiquidStatus = ZLiquidStatus.InWater;
|
data.LiquidStatus = ZLiquidStatus.InWater;
|
||||||
@@ -2160,7 +2156,7 @@ namespace Game.Maps
|
|||||||
if (gmap != null && (data.LiquidStatus == ZLiquidStatus.AboveWater || data.LiquidStatus == ZLiquidStatus.NoWater))
|
if (gmap != null && (data.LiquidStatus == ZLiquidStatus.AboveWater || data.LiquidStatus == ZLiquidStatus.NoWater))
|
||||||
{
|
{
|
||||||
LiquidData gridMapLiquid = new();
|
LiquidData gridMapLiquid = new();
|
||||||
ZLiquidStatus gridMapStatus = gmap.GetLiquidStatus(x, y, z, reqLiquidType, gridMapLiquid);
|
ZLiquidStatus gridMapStatus = gmap.GetLiquidStatus(x, y, z, reqLiquidType, gridMapLiquid, collisionHeight);
|
||||||
if (gridMapStatus != ZLiquidStatus.NoWater && (gridMapLiquid.level > vmapData.floorZ))
|
if (gridMapStatus != ZLiquidStatus.NoWater && (gridMapLiquid.level > vmapData.floorZ))
|
||||||
{
|
{
|
||||||
if (GetId() == 530 && gridMapLiquid.entry == 2)
|
if (GetId() == 530 && gridMapLiquid.entry == 2)
|
||||||
|
|||||||
@@ -103,9 +103,7 @@ namespace Game.Movement
|
|||||||
GetPoint(owner, ref destination);
|
GetPoint(owner, ref destination);
|
||||||
|
|
||||||
// Add LOS check for target point
|
// Add LOS check for target point
|
||||||
Position currentPosition = owner.GetPosition();
|
if (!owner.IsWithinLOS(destination.GetPositionX(), destination.GetPositionY(), destination.GetPositionZ()))
|
||||||
bool isInLOS = Global.VMapMgr.IsInLineOfSight(PhasingHandler.GetTerrainMapId(owner.GetPhaseShift(), owner.GetMap(), currentPosition.posX, currentPosition.posY), currentPosition.posX, currentPosition.posY, currentPosition.posZ + 2.0f, destination.GetPositionX(), destination.GetPositionY(), destination.GetPositionZ() + 2.0f, ModelIgnoreFlags.Nothing);
|
|
||||||
if (!isInLOS)
|
|
||||||
{
|
{
|
||||||
_timer.Reset(200);
|
_timer.Reset(200);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ namespace Game.Movement
|
|||||||
// Check both start and end points, if they're both in water, then we can *safely* let the creature move
|
// Check both start and end points, if they're both in water, then we can *safely* let the creature move
|
||||||
for (uint i = 0; i < _pathPoints.Length; ++i)
|
for (uint i = 0; i < _pathPoints.Length; ++i)
|
||||||
{
|
{
|
||||||
ZLiquidStatus status = _sourceUnit.GetMap().GetLiquidStatus(_sourceUnit.GetPhaseShift(), _pathPoints[i].X, _pathPoints[i].Y, _pathPoints[i].Z, MapConst.MapAllLiquidTypes);
|
ZLiquidStatus status = _sourceUnit.GetMap().GetLiquidStatus(_sourceUnit.GetPhaseShift(), _pathPoints[i].X, _pathPoints[i].Y, _pathPoints[i].Z, MapConst.MapAllLiquidTypes, _sourceUnit.GetCollisionHeight());
|
||||||
// One of the points is not in the water, cancel movement.
|
// One of the points is not in the water, cancel movement.
|
||||||
if (status == ZLiquidStatus.NoWater)
|
if (status == ZLiquidStatus.NoWater)
|
||||||
{
|
{
|
||||||
@@ -839,7 +839,7 @@ namespace Game.Movement
|
|||||||
NavTerrainFlag GetNavTerrain(float x, float y, float z)
|
NavTerrainFlag GetNavTerrain(float x, float y, float z)
|
||||||
{
|
{
|
||||||
LiquidData data;
|
LiquidData data;
|
||||||
ZLiquidStatus liquidStatus = _sourceUnit.GetMap().GetLiquidStatus(_sourceUnit.GetPhaseShift(), x, y, z, MapConst.MapAllLiquidTypes, out data);
|
ZLiquidStatus liquidStatus = _sourceUnit.GetMap().GetLiquidStatus(_sourceUnit.GetPhaseShift(), x, y, z, MapConst.MapAllLiquidTypes, out data, _sourceUnit.GetCollisionHeight());
|
||||||
if (liquidStatus == ZLiquidStatus.NoWater)
|
if (liquidStatus == ZLiquidStatus.NoWater)
|
||||||
return NavTerrainFlag.Ground;
|
return NavTerrainFlag.Ground;
|
||||||
|
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ namespace Game.Movement
|
|||||||
float moveTimeHalf = (float)(speedZ / gravity);
|
float moveTimeHalf = (float)(speedZ / gravity);
|
||||||
float dist = 2 * moveTimeHalf * speedXY;
|
float dist = 2 * moveTimeHalf * speedXY;
|
||||||
_owner.GetNearPoint2D(out float x, out float y, dist, _owner.GetOrientation() + angle);
|
_owner.GetNearPoint2D(out float x, out float y, dist, _owner.GetOrientation() + angle);
|
||||||
float z = _owner.GetPositionZ() + _owner.GetMidsectionHeight();
|
float z = _owner.GetPositionZ();
|
||||||
_owner.UpdateAllowedPositionZ(x, y, ref z);
|
_owner.UpdateAllowedPositionZ(x, y, ref z);
|
||||||
MoveJump(x, y, z, 0.0f, speedXY, speedZ);
|
MoveJump(x, y, z, 0.0f, speedXY, speedZ);
|
||||||
}
|
}
|
||||||
@@ -440,7 +440,7 @@ namespace Game.Movement
|
|||||||
if (_owner.IsFlying())
|
if (_owner.IsFlying())
|
||||||
point.Z = z;
|
point.Z = z;
|
||||||
else
|
else
|
||||||
point.Z = _owner.GetMap().GetHeight(_owner.GetPhaseShift(), point.X, point.Y, z);
|
point.Z = _owner.GetMapHeight(point.X, point.Y, z);
|
||||||
|
|
||||||
init.args.path[i] = point;
|
init.args.path[i] = point;
|
||||||
}
|
}
|
||||||
@@ -518,7 +518,7 @@ namespace Game.Movement
|
|||||||
public void MoveFall(uint id = 0)
|
public void MoveFall(uint id = 0)
|
||||||
{
|
{
|
||||||
// use larger distance for vmap height search than in most other cases
|
// use larger distance for vmap height search than in most other cases
|
||||||
float tz = _owner.GetMap().GetHeight(_owner.GetPhaseShift(), _owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ(), true, MapConst.MaxFallDistance);
|
float tz = _owner.GetMapHeight(_owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ(), true, MapConst.MaxFallDistance);
|
||||||
if (tz <= MapConst.InvalidHeight)
|
if (tz <= MapConst.InvalidHeight)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -858,10 +858,10 @@ namespace Game.Spells
|
|||||||
float angle = (float)RandomHelper.NextDouble() * (MathFunctions.PI * 35.0f / 180.0f) - (float)(Math.PI * 17.5f / 180.0f);
|
float angle = (float)RandomHelper.NextDouble() * (MathFunctions.PI * 35.0f / 180.0f) - (float)(Math.PI * 17.5f / 180.0f);
|
||||||
m_caster.GetClosePoint(out x, out y, out z, SharedConst.DefaultPlayerBoundingRadius, dis, angle);
|
m_caster.GetClosePoint(out x, out y, out z, SharedConst.DefaultPlayerBoundingRadius, dis, angle);
|
||||||
|
|
||||||
float ground = m_caster.GetMap().GetHeight(m_caster.GetPhaseShift(), x, y, z, true, 50.0f);
|
float ground = m_caster.GetMapHeight(x, y, z);
|
||||||
float liquidLevel = MapConst.VMAPInvalidHeightValue;
|
float liquidLevel = MapConst.VMAPInvalidHeightValue;
|
||||||
LiquidData liquidData;
|
LiquidData liquidData;
|
||||||
if (m_caster.GetMap().GetLiquidStatus(m_caster.GetPhaseShift(), x, y, z, MapConst.MapAllLiquidTypes, out liquidData) != 0)
|
if (m_caster.GetMap().GetLiquidStatus(m_caster.GetPhaseShift(), x, y, z, MapConst.MapAllLiquidTypes, out liquidData, m_caster.GetCollisionHeight()) != 0)
|
||||||
liquidLevel = liquidData.level;
|
liquidLevel = liquidData.level;
|
||||||
|
|
||||||
if (liquidLevel <= ground) // When there is no liquid Map.GetWaterOrGroundLevel returns ground level
|
if (liquidLevel <= ground) // When there is no liquid Map.GetWaterOrGroundLevel returns ground level
|
||||||
|
|||||||
Reference in New Issue
Block a user