context: using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Numerics;
  
  using ACE.DatLoader;
  using ACE.DatLoader.FileTypes;
  using ACE.Entity;
  using ACE.Server.Physics.Animation;
  using ACE.Server.Physics.BSP;
  using ACE.Server.Physics.Extensions;
  using ACE.Server.Managers;
  
  namespace ACE.Server.Physics.Common
  {
      public class Landblock: LandblockStruct
      {
          public Vector2 BlockCoord;
          public AFrame BlockFrame;
          public float MinZ;
          public float MaxZ;
          public bool DynObjsInitDone;
          public bool BlockInfoExists;
          public LandDefs.Direction Dir;
          public Vector2 Closest;
          public BoundingType InView;
          public CellLandblock _landblock;
          public LandblockInfo Info;
          public List<PhysicsObj> StaticObjects;
          public List<BuildingObj> Buildings;
          public List<ushort> StabList;
          public List<LandCell> DrawArray;
          public List<PhysicsObj> Scenery;
          public List<PhysicsObj> ServerObjects { get; set; }
  
          public static bool UseSceneFiles = true;
  
          public Landblock() : base()
          {
              Init();
          }
  
          public Landblock(CellLandblock landblock)
              : base(landblock)
          {
              Init();
  
              ID = landblock.Id;
              //Console.WriteLine("Loading landblock " + ID.ToString("X8"));
              BlockInfoExists = landblock.HasObjects;
              if (BlockInfoExists)
                  Info = DBObj.GetLandblockInfo(ID - 1);
              BlockCoord = LandDefs.blockid_to_lcoord(landblock.Id).Value;
              _landblock = landblock;
              get_land_limits();
          }
  
          public new void Init()
          {
              InView = BoundingType.Outside;
              Dir = LandDefs.Direction.Unknown;
              Closest = new Vector2(-1, -1);
              BlockCoord = new Vector2();
              StaticObjects = new List<PhysicsObj>();
              Buildings = new List<BuildingObj>();
              ServerObjects = new List<PhysicsObj>();
          }
  
          public void PostInit()
          {
              init_landcell();
  
              init_buildings();
              init_static_objs();
          }
  
          public void add_static_object(PhysicsObj obj)
          {
              StaticObjects.Add(obj);
          }
  
          /// <summary>
          /// Called when a server object to be broadcast to clients
          /// enters this landblock
          /// </summary>
          public bool add_server_object(PhysicsObj obj)
          {
              if (!ServerObjects.Contains(obj))
              {
                  ServerObjects.Add(obj);
                  return true;
              }
              return false;
          }
  
          /// <summary>
          /// Called when a server object to be broadcast to clients
          /// is removed from this landblock
          /// </summary>
          public bool remove_server_object(PhysicsObj obj)
          {
              return ServerObjects.Remove(obj);
          }
  
          public void adjust_scene_obj_height()
          {
              foreach (var obj in StaticObjects)
              {
                  var cell = (LandCell)obj.CurCell;
                  Polygon walkable = null;
                  var objPos = obj.Position.Frame.Origin;
                  if (!cell.find_terrain_poly(objPos, ref walkable))
                      continue;
                  var adjZ = objPos.Z;
                  if (Math.Abs(walkable.Plane.Normal.Z) > PhysicsGlobals.EPSILON)
                      adjZ = (objPos.Dot2D(walkable.Plane.Normal) + walkable.Plane.D) / walkable.Plane.Normal.Z * -1;
                  if (Math.Abs(objPos.Z - adjZ) > PhysicsGlobals.EPSILON)
                  {
                      objPos.Z = adjZ;
                      obj.set_initial_frame(obj.Position.Frame);
                  }
              }
          }
  
          public float GetZ(Vector3 point)
          {
              var cell = GetCell(point);
              if (cell == null)
                  return point.Z;
              Polygon walkable = null;
              if (!cell.find_terrain_poly(point, ref walkable))
                  return point.Z;
              var adjZ = point.Z;
              if (Math.Abs(walkable.Plane.Normal.Z) > PhysicsGlobals.EPSILON)
                  adjZ = (point.Dot2D(walkable.Plane.Normal) + walkable.Plane.D) / walkable.Plane.Normal.Z * -1;
              return adjZ;
          }
  
          public LandCell GetCell(Vector3 point)
          {
              if (point.X < 0 || point.Y < 0 || point.X > 192 || point.Y > 192)
                  return null;
  
              var cellX = (int)point.X / 24;
              var cellY = (int)point.Y / 24;
  
              var blockCellID = (ID & 0xFFFF0000) | (uint)(cellX * 8 + cellY) + 1;
              return (LandCell)LScape.get_landcell((uint)blockCellID);
          }
  
          public void destroy_buildings()
          {
              foreach (var building in Buildings)
                  building.remove();
  
              Buildings.Clear();
              StabList.Clear();
          }
  
          public void destroy_static_objects()
          {
              foreach (var obj in StaticObjects)
                  obj.leave_world();
  
              StaticObjects.Clear();
          }
  
          public void get_land_limits()
          {
              var minHeight = byte.MaxValue;
              var maxHeight = byte.MinValue;
  
              foreach (var height in Height)
              {
                  if (height < minHeight) minHeight = height;
                  if (height > maxHeight) maxHeight = height;
              }
              MinZ = LandDefs.LandHeightTable[minHeight]/* + 1.0f*/;
              MaxZ = LandDefs.LandHeightTable[maxHeight]/* + 200.0f*/;
          }
  
          public void get_land_scenes()
          {
              //Console.WriteLine("Loading scenery for " + ID.ToString("X8"));
              
              // ported from Scenery
              Scenery = new List<PhysicsObj>();
  
              // get the landblock cell offsets
              var blockX = (ID >> 24) * 8;
              var blockY = (ID >> 16 & 0xFF) * 8;
  
              for (uint i = 0; i < Terrain.Count; i++)
              {
                  var terrain = Terrain[(int)i];
  
                  var terrainType = terrain >> 2 & 0x1F;      // TerrainTypes table size = 32 (grass, desert, volcano, etc.)
                  var sceneType = terrain >> 11;              // SceneTypes table size = 89 total, 32 which can be indexed for each terrain type
  
                  var sceneInfo = (int)DatManager.PortalDat.RegionDesc.TerrainInfo.TerrainTypes[terrainType].SceneTypes[sceneType];
                  var scenes = DatManager.PortalDat.RegionDesc.SceneInfo.SceneTypes[sceneInfo].Scenes;
                  if (scenes.Count == 0) continue;
  
                  var cellX = i / LandDefs.VertexDim;
                  var cellY = i % LandDefs.VertexDim;
  
                  var globalCellX = (uint)(cellX + blockX);
                  var globalCellY = (uint)(cellY + blockY);
  
                  var cellMat = globalCellY * (712977289 * globalCellX + 1813693831) - 1109124029 * globalCellX + 2139937281;
                  var offset = cellMat * 2.3283064e-10;
                  var scene_idx = (int)(scenes.Count * offset);
                  if (scene_idx >= scenes.Count) scene_idx = 0;
  
                  var sceneId = scenes[scene_idx];
  
                  var scene = DatManager.PortalDat.ReadFromDat<Scene>(sceneId);
  
                  var cellXMat = -1109124029 * globalCellX;
                  var cellYMat = 1813693831 * globalCellY;
                  cellMat = 1360117743 * globalCellX * globalCellY + 1888038839;
  
                  for (uint j = 0; j < scene.Objects.Count; j++)
                  {
                      var obj = scene.Objects[(int)j];
                      var noise = (uint)(cellXMat + cellYMat - cellMat * (23399 + j)) * 2.3283064e-10;
  
                      if (noise < obj.Freq && obj.WeenieObj == 0)
                      {
                          // pseudo-randomized placement
                          var position = ObjectDesc.Displace(obj, globalCellX, globalCellY, j);
  
                          var lx = cellX * LandDefs.CellLength + position.X;
                          var ly = cellY * LandDefs.CellLength + position.Y;
                          var loc = new Vector3(lx, ly, position.Z);
  
                          // ensure within landblock range, and not near road
                          if (lx < 0 || ly < 0 || lx >= LandDefs.BlockLength || ly >= LandDefs.BlockLength || OnRoad(loc)) continue;
  
                          // load scenery
                          var pos = new Position(ID);
                          pos.Frame.Origin = loc;
                          var outside = LandDefs.AdjustToOutside(pos);
                          var cell = get_landcell(pos.ObjCellID);
                          if (cell == null) continue;
  
                          // check for buildings
                          var sortCell = (SortCell)cell;
                          if (sortCell != null && sortCell.has_building()) continue;
  
                          Polygon walkable = null;
                          var terrainPoly = cell.find_terrain_poly(pos.Frame.Origin, ref walkable);
                          if (walkable == null) continue;
  
                          // ensure walkable slope
                          if (!ObjectDesc.CheckSlope(obj, walkable.Plane.Normal.Z)) continue;
  
                          walkable.Plane.set_height(ref pos.Frame.Origin);
  
                          // rotation
                          if (obj.Align != 0)
                              pos.Frame = ObjectDesc.ObjAlign(obj, walkable.Plane, pos.Frame.Origin);
                          else
                              pos.Frame = ObjectDesc.RotateObj(obj, globalCellX, globalCellY, j, pos.Frame.Origin);
  
                          // build object
                          var physicsObj = PhysicsObj.makeObject(obj.ObjId, 0, false);
                          physicsObj.DatObject = true;
                          physicsObj.set_initial_frame(pos.Frame);
                          if (!physicsObj.obj_within_block())
                          {
                              //Console.WriteLine($"Landblock {ID:X8} scenery: failed to spawn {obj.ObjId:X8}");
                              physicsObj.DestroyObject();
                              continue;
                          }
  
                          physicsObj.add_obj_to_cell(cell, pos.Frame);
                          var scale = ObjectDesc.ScaleObj(obj, globalCellX, globalCellY, j);
                          physicsObj.SetScaleStatic(scale);
                          Scenery.Add(physicsObj);
                      }
                  }
              }
              //Console.WriteLine("Landblock " + ID.ToString("X8") + " scenery count: " + Scenery.Count);
          }
  
          public static float RoadWidth = 5.0f;
          public static float TileLength = 24.0f;
  
          /// <summary>
          /// Returns TRUE if x,y is located on a road cell
          /// </summary>
          public bool IsRoad(DatLoader.Entity.ObjectDesc obj, float x, float y)
          {
              var cellX = (int)Math.Floor(x / LandDefs.CellLength);
              var cellY = (int)Math.Floor(y / LandDefs.CellLength);
              var terrain = Terrain[cellX * LandDefs.BlockSide + cellY];     // ensure within bounds?
              return (terrain & 0x3) != 0;    // TODO: more complicated check for within road range
          }
  
          public bool OnRoad(Vector3 obj)
          {
              int x = (int)(obj.X / TileLength);
              int y = (int)(obj.Y / TileLength);
  
              float rMin = RoadWidth;
              float rMax = TileLength - RoadWidth;
  
              int x0 = x;
              int x1 = x0 + 1;
              int y0 = y;
              int y1 = y0 + 1;
  
              uint r0 = GetRoad(x0, y0);
              uint r1 = GetRoad(x0, y1);
              uint r2 = GetRoad(x1, y0);
              uint r3 = GetRoad(x1, y1);
  
              if (r0 == 0 && r1 == 0 && r2 == 0 && r3 == 0)
                  return false;
  
              float dx = obj.X - x * TileLength;
              float dy = obj.Y - y * TileLength;
  
              if (r0 > 0)
              {
                  if (r1 > 0)
                  {
                      if (r2 > 0)
                      {
                          if (r3 > 0)
                              return true;
                          else
                              return (dx < rMin || dy < rMin);
                      }
                      else
                      {
                          if (r3 > 0)
                              return (dx < rMin || dy > rMax);
                          else
                              return (dx < rMin);
                      }
                  }
                  else
                  {
                      if (r2 > 0)
                      {
                          if (r3 > 0)
                              return (dx > rMax || dy < rMin);
                          else
                              return (dy < rMin);
                      }
                      else
                      {
                          if (r3 > 0)
                              return (Math.Abs(dx - dy) < rMin);
                          else
                              return (dx + dy < rMin);
                      }
                  }
              }
              else
              {
                  if (r1 > 0)
                  {
                      if (r2 > 0)
                      {
                          if (r3 > 0)
                              return (dx > rMax || dy > rMax);
                          else
                              return (Math.Abs(dx + dy - TileLength) < rMin);
                      }
                      else
                      {
                          if (r3 > 0)
                              return (dy > rMax);
                          else
                              return (TileLength + dx - dy < rMin);
                      }
                  }
                  else
                  {
                      if (r2 > 0)
                      {
                          if (r3 > 0)
                              return (dx > rMax);
                          else
  
gt:                             return (TileLength - dx + dy < rMin);