2018-10-03 15:03:05 -04:00
using System ;
2018-08-21 10:25:26 -07:00
using System.Numerics ;
2019-04-18 06:56:58 -05:00
2020-08-15 08:32:27 -04:00
using log4net ;
2021-11-28 13:11:12 -05:00
using ACE.DatLoader ;
2019-04-18 06:56:58 -05:00
using ACE.Entity ;
2020-08-15 08:32:27 -04:00
using ACE.Entity.Enum.Properties ;
2018-08-21 10:25:26 -07:00
using ACE.Server.Physics.Common ;
using ACE.Server.Physics.Extensions ;
using ACE.Server.Physics.Util ;
2020-08-15 08:32:27 -04:00
using ACE.Server.WorldObjects ;
2019-04-18 06:56:58 -05:00
2018-08-21 10:25:26 -07:00
using Position = ACE . Entity . Position ;
namespace ACE.Server.Entity
{
public static class PositionExtensions
{
2020-08-15 08:32:27 -04:00
private static readonly ILog log = LogManager . GetLogger ( System . Reflection . MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
2022-02-04 03:56:01 -05:00
public static Vector3 ToGlobal ( this Position p , bool skipIndoors = false )
2018-08-21 10:25:26 -07:00
{
2019-04-18 06:56:58 -05:00
// TODO: Is this necessary? It seemed to be loading rogue physics landblocks. Commented out 2019-04 Mag-nus
//var landblock = LScape.get_landblock(p.LandblockId.Raw);
2018-08-21 10:25:26 -07:00
2019-01-15 16:32:03 -05:00
// TODO: investigate dungeons that are below actual traversable overworld terrain
// ex., 010AFFFF
//if (landblock.IsDungeon)
2019-12-31 01:51:40 -05:00
if ( p . Indoors & & skipIndoors )
2018-08-21 10:25:26 -07:00
return p . Pos ;
var x = p . LandblockId . LandblockX * Position . BlockLength + p . PositionX ;
var y = p . LandblockId . LandblockY * Position . BlockLength + p . PositionY ;
var z = p . PositionZ ;
return new Vector3 ( x , y , z ) ;
}
public static Position FromGlobal ( this Position p , Vector3 pos )
{
2019-04-18 06:56:58 -05:00
// TODO: Is this necessary? It seemed to be loading rogue physics landblocks. Commented out 2019-04 Mag-nus
//var landblock = LScape.get_landblock(p.LandblockId.Raw);
2018-08-21 10:25:26 -07:00
2019-01-15 16:32:03 -05:00
// TODO: investigate dungeons that are below actual traversable overworld terrain
// ex., 010AFFFF
//if (landblock.IsDungeon)
if ( p . Indoors )
2018-08-21 10:25:26 -07:00
{
var iPos = new Position ( ) ;
iPos . LandblockId = p . LandblockId ;
iPos . Pos = new Vector3 ( pos . X , pos . Y , pos . Z ) ;
iPos . Rotation = p . Rotation ;
iPos . LandblockId = new LandblockId ( GetCell ( iPos ) ) ;
return iPos ;
}
var blockX = ( uint ) pos . X / Position . BlockLength ;
var blockY = ( uint ) pos . Y / Position . BlockLength ;
var localX = pos . X % Position . BlockLength ;
var localY = pos . Y % Position . BlockLength ;
var landblockID = blockX < < 24 | blockY < < 16 | 0xFFFF ;
var position = new Position ( ) ;
position . LandblockId = new LandblockId ( ( byte ) blockX , ( byte ) blockY ) ;
position . PositionX = localX ;
position . PositionY = localY ;
position . PositionZ = pos . Z ;
position . Rotation = p . Rotation ;
position . LandblockId = new LandblockId ( GetCell ( position ) ) ;
return position ;
}
/// <summary>
/// Gets the cell ID for a position within a landblock
/// </summary>
public static uint GetCell ( this Position p )
{
2024-09-15 22:33:48 -04:00
try
{
//var landblock = LScape.get_landblock(p.LandblockId.Raw);
2018-12-11 19:59:28 -06:00
2024-09-15 22:33:48 -04:00
// dungeons
// TODO: investigate dungeons that are below actual traversable overworld terrain
// ex., 010AFFFF
//if (landblock.IsDungeon)
if ( p . Indoors )
return GetIndoorCell ( p ) ;
2018-12-11 19:59:28 -06:00
2024-09-15 22:33:48 -04:00
// outside - could be on landscape, in building, or underground cave
var cellID = GetOutdoorCell ( p ) ;
var landcell = LScape . get_landcell ( cellID ) as LandCell ;
2018-08-21 10:25:26 -07:00
2024-09-15 22:33:48 -04:00
if ( landcell = = null )
return cellID ;
2018-08-21 10:25:26 -07:00
2024-09-15 22:33:48 -04:00
if ( landcell . has_building ( ) )
2018-08-21 10:25:26 -07:00
{
2024-09-15 22:33:48 -04:00
var envCells = landcell . Building . get_building_cells ( ) ;
2018-08-21 10:25:26 -07:00
foreach ( var envCell in envCells )
if ( envCell . point_in_cell ( p . Pos ) )
return envCell . ID ;
}
2024-09-15 22:33:48 -04:00
// handle underground areas ie. caves
// get the terrain Z-height for this X/Y
Physics . Polygon walkable = null ;
var terrainPoly = landcell . find_terrain_poly ( p . Pos , ref walkable ) ;
if ( walkable ! = null )
{
Vector3 terrainPos = p . Pos ;
walkable . Plane . set_height ( ref terrainPos ) ;
// are we below ground? if so, search all of the indoor cells for this landblock
if ( terrainPos . Z > p . Pos . Z )
{
var landblock = LScape . get_landblock ( p . LandblockId . Raw ) ;
var envCells = landblock . get_envcells ( ) ;
foreach ( var envCell in envCells )
if ( envCell . point_in_cell ( p . Pos ) )
return envCell . ID ;
}
}
return cellID ;
2018-08-21 10:25:26 -07:00
}
2024-09-15 22:33:48 -04:00
catch ( Exception e )
{
log . ErrorFormat ( "GetCell() threw an exception: {0}\nposition as LOC => {1}" , e . ToString ( ) , p . ToLOCString ( ) ) ;
log . Error ( e ) ;
2018-12-11 19:59:28 -06:00
2024-09-15 22:33:48 -04:00
return 0 ;
}
2018-08-21 10:25:26 -07:00
}
/// <summary>
/// Gets an outdoor cell ID for a position within a landblock
/// </summary>
2018-12-28 01:48:05 -05:00
public static uint GetOutdoorCell ( this Position p )
2018-08-21 10:25:26 -07:00
{
var cellX = ( uint ) p . PositionX / Position . CellLength ;
var cellY = ( uint ) p . PositionY / Position . CellLength ;
var cellID = cellX * Position . CellSide + cellY + 1 ;
var blockCellID = ( uint ) ( ( p . LandblockId . Raw & 0xFFFF0000 ) | cellID ) ;
return blockCellID ;
}
/// <summary>
/// Gets an indoor cell ID for a position within a dungeon
/// </summary>
private static uint GetIndoorCell ( this Position p )
{
var adjustCell = AdjustCell . Get ( p . Landblock ) ;
var envCell = adjustCell . GetCell ( p . Pos ) ;
if ( envCell ! = null )
return envCell . Value ;
else
return p . Cell ;
}
2018-10-03 15:03:05 -04:00
/// <summary>
/// Returns the greatest single-dimension square distance between 2 positions
/// </summary>
public static uint CellDist ( this Position p1 , Position p2 )
{
if ( ! p1 . Indoors & & ! p2 . Indoors )
{
return Math . Max ( p1 . GlobalCellX , p1 . GlobalCellY ) ;
}
// handle dungeons
/ * var block1 = LScape . get_landblock ( p1 . LandblockId . Raw ) ;
var block2 = LScape . get_landblock ( p2 . LandblockId . Raw ) ;
if ( block1 . IsDungeon | | block2 . IsDungeon )
{
// 2 separate dungeons = infinite distance
if ( block1 . ID ! = block2 . ID )
return uint . MaxValue ;
return GetDungeonCellDist ( p1 , p2 ) ;
} * /
var _p1 = new Position ( p1 ) ;
var _p2 = new Position ( p2 ) ;
if ( _p1 . Indoors )
_p1 . LandblockId = new LandblockId ( _p1 . GetOutdoorCell ( ) ) ;
if ( _p2 . Indoors )
_p2 . LandblockId = new LandblockId ( _p2 . GetIndoorCell ( ) ) ;
return Math . Max ( _p1 . GlobalCellX , _p2 . GlobalCellY ) ;
}
public static uint GetDungeonCellDist ( Position p1 , Position p2 )
{
// not implemented yet
return uint . MaxValue ;
}
2018-12-20 02:42:54 -05:00
public static Vector2 ? GetMapCoords ( this Position pos )
{
// no map coords available for dungeons / indoors?
if ( ( pos . Cell & 0xFFFF ) > = 0x100 )
return null ;
var globalPos = pos . ToGlobal ( ) ;
// 1 landblock = 192 meters
// 1 landblock = 0.8 map units
// 1 map unit = 1.25 landblocks
// 1 map unit = 240 meters
var mapCoords = new Vector2 ( globalPos . X / 240 , globalPos . Y / 240 ) ;
// dereth is 204 map units across, -102 to +102
mapCoords - = Vector2 . One * 102 ;
return mapCoords ;
}
public static string GetMapCoordStr ( this Position pos )
{
var mapCoords = pos . GetMapCoords ( ) ;
if ( mapCoords = = null )
return null ;
var northSouth = mapCoords . Value . Y > = 0 ? "N" : "S" ;
var eastWest = mapCoords . Value . X > = 0 ? "E" : "W" ;
return string . Format ( "{0:0.0}" , Math . Abs ( mapCoords . Value . Y ) - 0.05f ) + northSouth + ", "
+ string . Format ( "{0:0.0}" , Math . Abs ( mapCoords . Value . X ) - 0.05f ) + eastWest ;
}
2018-12-20 07:49:03 -05:00
public static void AdjustMapCoords ( this Position pos )
{
// adjust Z to terrain height
pos . PositionZ = pos . GetTerrainZ ( ) ;
// adjust to building height, if applicable
var sortCell = LScape . get_landcell ( pos . Cell ) as SortCell ;
if ( sortCell ! = null & & sortCell . has_building ( ) )
{
var building = sortCell . Building ;
var minZ = building . GetMinZ ( ) ;
2018-12-21 22:54:11 -05:00
if ( minZ > 0 & & minZ < float . MaxValue )
2018-12-20 07:49:03 -05:00
pos . PositionZ + = minZ ;
pos . LandblockId = new LandblockId ( pos . GetCell ( ) ) ;
}
}
2021-11-28 13:11:12 -05:00
public static void Translate ( this Position pos , uint blockCell )
{
var newBlockX = blockCell > > 24 ;
var newBlockY = ( blockCell > > 16 ) & 0xFF ;
var xDiff = ( int ) newBlockX - pos . LandblockX ;
var yDiff = ( int ) newBlockY - pos . LandblockY ;
//pos.Origin.X -= xDiff * 192;
pos . PositionX - = xDiff * 192 ;
//pos.Origin.Y -= yDiff * 192;
pos . PositionY - = yDiff * 192 ;
//pos.ObjCellID = blockCell;
pos . LandblockId = new LandblockId ( blockCell ) ;
}
public static void FindZ ( this Position pos )
{
var envCell = DatManager . CellDat . ReadFromDat < DatLoader . FileTypes . EnvCell > ( pos . Cell ) ;
pos . PositionZ = envCell . Position . Origin . Z ;
}
2018-12-20 07:49:03 -05:00
public static float GetTerrainZ ( this Position p )
{
var landblock = LScape . get_landblock ( p . LandblockId . Raw ) ;
var cellID = GetOutdoorCell ( p ) ;
var landcell = ( LandCell ) LScape . get_landcell ( cellID ) ;
if ( landcell = = null )
return p . Pos . Z ;
Physics . Polygon walkable = null ;
if ( ! landcell . find_terrain_poly ( p . Pos , ref walkable ) )
return p . Pos . Z ;
Vector3 terrainPos = p . Pos ;
walkable . Plane . set_height ( ref terrainPos ) ;
return terrainPos . Z ;
}
2019-01-23 15:20:37 -05:00
/// <summary>
/// Returns TRUE if outdoor position is located on walkable slope
/// </summary>
public static bool IsWalkable ( this Position p )
{
if ( p . Indoors ) return true ;
var landcell = ( LandCell ) LScape . get_landcell ( p . Cell ) ;
Physics . Polygon walkable = null ;
var terrainPoly = landcell . find_terrain_poly ( p . Pos , ref walkable ) ;
2019-01-24 23:03:25 -05:00
if ( walkable = = null ) return false ;
2019-01-23 15:20:37 -05:00
2019-01-24 23:03:25 -05:00
return Physics . PhysicsObj . is_valid_walkable ( walkable . Plane . Normal ) ;
2019-01-23 15:20:37 -05:00
}
2019-08-05 16:38:48 -04:00
/// <summary>
/// Returns TRUE if current cell is a House cell
/// </summary>
public static bool IsRestrictable ( this Position p , Landblock landblock )
{
var cell = landblock . IsDungeon ? p . Cell : p . GetOutdoorCell ( ) ;
return HouseCell . HouseCells . ContainsKey ( cell ) ;
}
2020-03-22 11:20:44 -04:00
public static Position ACEPosition ( this Physics . Common . Position pos )
{
return new Position ( pos . ObjCellID , pos . Frame . Origin , pos . Frame . Orientation ) ;
}
2020-03-23 20:56:34 -04:00
public static Physics . Common . Position PhysPosition ( this Position pos )
{
return new Physics . Common . Position ( pos . Cell , new Physics . Animation . AFrame ( pos . Pos , pos . Rotation ) ) ;
}
2020-08-15 08:32:27 -04:00
// differs from ac physics engine
public static readonly float RotationEpsilon = 0.0001f ;
public static bool IsRotationValid ( this Quaternion q )
{
if ( q = = Quaternion . Identity )
return true ;
if ( float . IsNaN ( q . X ) | | float . IsNaN ( q . Y ) | | float . IsNaN ( q . Z ) | | float . IsNaN ( q . W ) )
return false ;
var length = q . Length ( ) ;
if ( float . IsNaN ( length ) )
return false ;
if ( Math . Abs ( 1.0f - length ) > RotationEpsilon )
return false ;
return true ;
}
public static bool AttemptToFixRotation ( this Position pos , WorldObject wo , PositionType positionType )
{
log . Warn ( $"detected bad quaternion x y z w for {wo.Name} (0x{wo.Guid}) | WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType} | PositionType: {positionType}" ) ;
log . Warn ( $"before fix: {pos.ToLOCString()}" ) ;
var normalized = Quaternion . Normalize ( pos . Rotation ) ;
var success = IsRotationValid ( normalized ) ;
if ( success )
pos . Rotation = normalized ;
log . Warn ( $" after fix: {pos.ToLOCString()}" ) ;
return success ;
}
2018-08-21 10:25:26 -07:00
}
}