Ported .Net Core commits:
hondacrx: - Initial commit: Switch to .Net Core 2.0 - Fix build and removed not needed files Fabi: - Updated solution platforms. - Changed folder structure. - Change library target framework to netstandard2.0. - Updated solution platforms again... - Removed windows specific kernel32 function usage (Ctrl-C handler).
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Game.Entities;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class AreaBoundary
|
||||
{
|
||||
public AreaBoundary(BoundaryType bType, bool isInverted)
|
||||
{
|
||||
m_boundaryType = bType;
|
||||
m_isInvertedBoundary = isInverted;
|
||||
}
|
||||
|
||||
public BoundaryType GetBoundaryType() { return m_boundaryType; }
|
||||
|
||||
public bool IsWithinBoundary(Position pos) { return (IsWithinBoundaryArea(pos) != m_isInvertedBoundary); }
|
||||
|
||||
public virtual bool IsWithinBoundaryArea(Position pos) { return false; }
|
||||
|
||||
BoundaryType m_boundaryType;
|
||||
bool m_isInvertedBoundary;
|
||||
|
||||
public enum BoundaryType
|
||||
{
|
||||
Rectangle, // Rectangle Aligned With The Coordinate Axis
|
||||
Circle,
|
||||
Ellipse,
|
||||
Triangle,
|
||||
Parallelogram,
|
||||
ZRange,
|
||||
}
|
||||
|
||||
public class DoublePosition : Position
|
||||
{
|
||||
public DoublePosition(double x = 0.0, double y = 0.0, double z = 0.0, float o = 0f) : base((float)x, (float)y, (float)z, o)
|
||||
{
|
||||
d_positionX = x;
|
||||
d_positionY = y;
|
||||
d_positionZ = z;
|
||||
}
|
||||
public DoublePosition(float x, float y = 0f, float z = 0f, float o = 0f) : base(x, y, z, o)
|
||||
{
|
||||
d_positionX = x;
|
||||
d_positionY = y;
|
||||
d_positionZ = z;
|
||||
}
|
||||
public DoublePosition(Position pos) : this(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()) { }
|
||||
|
||||
public double GetDoublePositionX() { return d_positionX; }
|
||||
public double GetDoublePositionY() { return d_positionY; }
|
||||
public double GetDoublePositionZ() { return d_positionZ; }
|
||||
|
||||
public double GetDoubleExactDist2dSq(DoublePosition pos)
|
||||
{
|
||||
double offX = GetDoublePositionX() - pos.GetDoublePositionX();
|
||||
double offY = GetDoublePositionY() - pos.GetDoublePositionY();
|
||||
return (offX * offX) + (offY * offY);
|
||||
}
|
||||
|
||||
public Position sync()
|
||||
{
|
||||
posX = (float)d_positionX;
|
||||
posY = (float)d_positionY;
|
||||
posZ = (float)d_positionZ;
|
||||
return this;
|
||||
}
|
||||
|
||||
double d_positionX;
|
||||
double d_positionY;
|
||||
double d_positionZ;
|
||||
}
|
||||
}
|
||||
|
||||
public class RectangleBoundary : AreaBoundary
|
||||
{
|
||||
// X axis is north/south, Y axis is east/west, larger values are northwest
|
||||
public RectangleBoundary(float southX, float northX, float eastY, float westY, bool isInverted = false) : base(BoundaryType.Rectangle, isInverted)
|
||||
{
|
||||
_minX = southX;
|
||||
_maxX = northX;
|
||||
_minY = eastY;
|
||||
_maxY = westY;
|
||||
}
|
||||
|
||||
public override bool IsWithinBoundaryArea(Position pos)
|
||||
{
|
||||
if (pos == null)
|
||||
return false;
|
||||
|
||||
return !(pos.GetPositionX() < _minX || pos.GetPositionX() > _maxX || pos.GetPositionY() < _minY || pos.GetPositionY() > _maxY);
|
||||
}
|
||||
|
||||
float _minX;
|
||||
float _maxX;
|
||||
float _minY;
|
||||
float _maxY;
|
||||
}
|
||||
|
||||
public class CircleBoundary : AreaBoundary
|
||||
{
|
||||
public CircleBoundary(Position center, double radius, bool isInverted = false) : this(new DoublePosition(center), radius, isInverted) { }
|
||||
public CircleBoundary(Position center, Position pointOnCircle, bool isInverted = false) : this(new DoublePosition(center), new DoublePosition(pointOnCircle), isInverted) { }
|
||||
public CircleBoundary(DoublePosition center, double radius, bool isInverted = false) : base(BoundaryType.Circle, isInverted)
|
||||
{
|
||||
_center = center;
|
||||
_radiusSq = radius * radius;
|
||||
}
|
||||
public CircleBoundary(DoublePosition center, DoublePosition pointOnCircle, bool isInverted = false) : base(BoundaryType.Circle, isInverted)
|
||||
{
|
||||
_center = center;
|
||||
_radiusSq = center.GetDoubleExactDist2dSq(pointOnCircle);
|
||||
}
|
||||
|
||||
public override bool IsWithinBoundaryArea(Position pos)
|
||||
{
|
||||
if (pos == null)
|
||||
return false;
|
||||
|
||||
double offX = _center.GetDoublePositionX() - pos.GetPositionX();
|
||||
double offY = _center.GetDoublePositionY() - pos.GetPositionY();
|
||||
return offX * offX + offY * offY <= _radiusSq;
|
||||
}
|
||||
|
||||
DoublePosition _center;
|
||||
double _radiusSq;
|
||||
}
|
||||
|
||||
public class EllipseBoundary : AreaBoundary
|
||||
{
|
||||
public EllipseBoundary(Position center, double radiusX, double radiusY, bool isInverted = false) : this(new DoublePosition(center), radiusX, radiusY, isInverted) { }
|
||||
public EllipseBoundary(DoublePosition center, double radiusX, double radiusY, bool isInverted = false) :base(BoundaryType.Ellipse, isInverted)
|
||||
{
|
||||
_center = center;
|
||||
_radiusYSq = radiusY * radiusY;
|
||||
_scaleXSq = _radiusYSq / (radiusX * radiusX);
|
||||
}
|
||||
|
||||
public override bool IsWithinBoundaryArea(Position pos)
|
||||
{
|
||||
if (pos == null)
|
||||
return false;
|
||||
|
||||
double offX = _center.GetDoublePositionX() - pos.GetPositionX(), offY = _center.GetDoublePositionY() - pos.GetPositionY();
|
||||
return (offX * offX) * _scaleXSq + (offY * offY) <= _radiusYSq;
|
||||
}
|
||||
|
||||
DoublePosition _center;
|
||||
double _radiusYSq;
|
||||
double _scaleXSq;
|
||||
}
|
||||
|
||||
public class TriangleBoundary : AreaBoundary
|
||||
{
|
||||
public TriangleBoundary(Position pointA, Position pointB, Position pointC, bool isInverted = false)
|
||||
: this(new DoublePosition(pointA), new DoublePosition(pointB), new DoublePosition(pointC), isInverted) { }
|
||||
public TriangleBoundary(DoublePosition pointA, DoublePosition pointB, DoublePosition pointC, bool isInverted = false) : base(BoundaryType.Triangle, isInverted)
|
||||
{
|
||||
_a = pointA;
|
||||
_b = pointB;
|
||||
_c = pointC;
|
||||
_abx = _b.GetDoublePositionX() - _a.GetDoublePositionX();
|
||||
_bcx = _c.GetDoublePositionX() - _b.GetDoublePositionX();
|
||||
_cax = _a.GetDoublePositionX() - _c.GetDoublePositionX();
|
||||
_aby = _b.GetDoublePositionY() - _a.GetDoublePositionY();
|
||||
_bcy = _c.GetDoublePositionY() - _b.GetDoublePositionY();
|
||||
_cay = _a.GetDoublePositionY() - _c.GetDoublePositionY();
|
||||
}
|
||||
|
||||
public override bool IsWithinBoundaryArea(Position pos)
|
||||
{
|
||||
if (pos == null)
|
||||
return false;
|
||||
|
||||
// half-plane signs
|
||||
bool sign1 = ((-_b.GetDoublePositionX() + pos.GetPositionX()) * _aby - (-_b.GetDoublePositionY() + pos.GetPositionY()) * _abx) < 0;
|
||||
bool sign2 = ((-_c.GetDoublePositionX() + pos.GetPositionX()) * _bcy - (-_c.GetDoublePositionY() + pos.GetPositionY()) * _bcx) < 0;
|
||||
bool sign3 = ((-_a.GetDoublePositionX() + pos.GetPositionX()) * _cay - (-_a.GetDoublePositionY() + pos.GetPositionY()) * _cax) < 0;
|
||||
|
||||
// if all signs are the same, the point is inside the triangle
|
||||
return ((sign1 == sign2) && (sign2 == sign3));
|
||||
}
|
||||
|
||||
DoublePosition _a;
|
||||
DoublePosition _b;
|
||||
DoublePosition _c;
|
||||
double _abx;
|
||||
double _bcx;
|
||||
double _cax;
|
||||
double _aby;
|
||||
double _bcy;
|
||||
double _cay;
|
||||
}
|
||||
|
||||
public class ParallelogramBoundary : AreaBoundary
|
||||
{
|
||||
// Note: AB must be orthogonal to AD
|
||||
public ParallelogramBoundary(Position cornerA, Position cornerB, Position cornerD, bool isInverted = false)
|
||||
: this(new DoublePosition(cornerA), new DoublePosition(cornerB), new DoublePosition(cornerD), isInverted) { }
|
||||
public ParallelogramBoundary(DoublePosition cornerA, DoublePosition cornerB, DoublePosition cornerD, bool isInverted = false) : base(BoundaryType.Parallelogram, isInverted)
|
||||
{
|
||||
_a = cornerA;
|
||||
_b = cornerB;
|
||||
_d = cornerD;
|
||||
_c = new DoublePosition(_d.GetDoublePositionX() + (_b.GetDoublePositionX() - _a.GetDoublePositionX()), _d.GetDoublePositionY() + (_b.GetDoublePositionY() - _a.GetDoublePositionY()));
|
||||
_abx = _b.GetDoublePositionX() - _a.GetDoublePositionX();
|
||||
_dax = _a.GetDoublePositionX() - _d.GetDoublePositionX();
|
||||
_aby = _b.GetDoublePositionY() - _a.GetDoublePositionY();
|
||||
_day = _a.GetDoublePositionY() - _d.GetDoublePositionY();
|
||||
}
|
||||
|
||||
public override bool IsWithinBoundaryArea(Position pos)
|
||||
{
|
||||
if (pos == null)
|
||||
return false;
|
||||
|
||||
// half-plane signs
|
||||
bool sign1 = ((-_b.GetDoublePositionX() + pos.GetPositionX()) * _aby - (-_b.GetDoublePositionY() + pos.GetPositionY()) * _abx) < 0;
|
||||
bool sign2 = ((-_a.GetDoublePositionX() + pos.GetPositionX()) * _day - (-_a.GetDoublePositionY() + pos.GetPositionY()) * _dax) < 0;
|
||||
bool sign3 = ((-_d.GetDoublePositionY() + pos.GetPositionY()) * _abx - (-_d.GetDoublePositionX() + pos.GetPositionX()) * _aby) < 0; // AB = -CD
|
||||
bool sign4 = ((-_c.GetDoublePositionY() + pos.GetPositionY()) * _dax - (-_c.GetDoublePositionX() + pos.GetPositionX()) * _day) < 0; // DA = -BC
|
||||
|
||||
// if all signs are equal, the point is inside
|
||||
return ((sign1 == sign2) && (sign2 == sign3) && (sign3 == sign4));
|
||||
}
|
||||
|
||||
DoublePosition _a;
|
||||
DoublePosition _b;
|
||||
DoublePosition _d;
|
||||
DoublePosition _c;
|
||||
double _abx;
|
||||
double _dax;
|
||||
double _aby;
|
||||
double _day;
|
||||
}
|
||||
|
||||
public class ZRangeBoundary : AreaBoundary
|
||||
{
|
||||
public ZRangeBoundary(float minZ, float maxZ, bool isInverted = false) : base(BoundaryType.ZRange, isInverted)
|
||||
{
|
||||
_minZ = minZ;
|
||||
_maxZ = maxZ;
|
||||
}
|
||||
|
||||
public override bool IsWithinBoundaryArea(Position pos)
|
||||
{
|
||||
if (pos == null)
|
||||
return false;
|
||||
|
||||
return !(pos.GetPositionZ() < _minZ || pos.GetPositionZ() > _maxZ);
|
||||
}
|
||||
|
||||
float _minZ;
|
||||
float _maxZ;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Entities;
|
||||
using System;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class Cell
|
||||
{
|
||||
public Cell(ICoord p)
|
||||
{
|
||||
data.grid_x = p.x_coord / MapConst.MaxCells;
|
||||
data.grid_y = p.y_coord / MapConst.MaxCells;
|
||||
data.cell_x = p.x_coord % MapConst.MaxCells;
|
||||
data.cell_y = p.y_coord % MapConst.MaxCells;
|
||||
}
|
||||
|
||||
public Cell(float x, float y)
|
||||
{
|
||||
ICoord p = GridDefines.ComputeCellCoord(x, y);
|
||||
data.grid_x = p.x_coord / MapConst.MaxCells;
|
||||
data.grid_y = p.y_coord / MapConst.MaxCells;
|
||||
data.cell_x = p.x_coord % MapConst.MaxCells;
|
||||
data.cell_y = p.y_coord % MapConst.MaxCells;
|
||||
}
|
||||
|
||||
public Cell(Cell cell) { data = cell.data; }
|
||||
|
||||
public bool IsCellValid()
|
||||
{
|
||||
return data.cell_x < MapConst.MaxCells && data.cell_y < MapConst.MaxCells;
|
||||
}
|
||||
|
||||
public uint GetId()
|
||||
{
|
||||
return data.grid_x * MapConst.MaxGrids + data.grid_y;
|
||||
}
|
||||
|
||||
public uint GetCellX() { return data.cell_x; }
|
||||
public uint GetCellY() { return data.cell_y; }
|
||||
public uint GetGridX() { return data.grid_x; }
|
||||
public uint GetGridY() { return data.grid_y; }
|
||||
public bool NoCreate() { return data.nocreate; }
|
||||
public void SetNoCreate() { data.nocreate = true; }
|
||||
|
||||
public string GetGridCellString()
|
||||
{
|
||||
return string.Format("grid[{0}, {1}]cell[{2}, {3}]", GetGridX(), GetGridY(), GetCellX(), GetCellY());
|
||||
}
|
||||
|
||||
public struct Data
|
||||
{
|
||||
public uint grid_x;
|
||||
public uint grid_y;
|
||||
public uint cell_x;
|
||||
public uint cell_y;
|
||||
public bool nocreate;
|
||||
}
|
||||
public Data data;
|
||||
|
||||
public CellCoord GetCellCoord()
|
||||
{
|
||||
return new CellCoord(
|
||||
data.grid_x * MapConst.MaxCells + data.cell_x,
|
||||
data.grid_y * MapConst.MaxCells + data.cell_y);
|
||||
}
|
||||
|
||||
public bool DiffCell(Cell cell)
|
||||
{
|
||||
return (data.cell_x != cell.data.cell_x ||
|
||||
data.cell_y != cell.data.cell_y);
|
||||
}
|
||||
|
||||
public bool DiffGrid(Cell cell)
|
||||
{
|
||||
return (data.grid_x != cell.data.grid_x ||
|
||||
data.grid_y != cell.data.grid_y);
|
||||
}
|
||||
|
||||
public void Visit(CellCoord standing_cell, Visitor visitor, Map map, WorldObject obj, float radius)
|
||||
{
|
||||
//we should increase search radius by object's radius, otherwise
|
||||
//we could have problems with huge creatures, which won't attack nearest players etc
|
||||
Visit(standing_cell, visitor, map, obj.GetPositionX(), obj.GetPositionY(), radius + obj.GetObjectSize());
|
||||
}
|
||||
|
||||
public void Visit(CellCoord standing_cell, Visitor visitor, Map map, float x_off, float y_off, float radius)
|
||||
{
|
||||
if (!standing_cell.IsCoordValid())
|
||||
return;
|
||||
|
||||
//no jokes here... Actually placing ASSERT() here was good idea, but
|
||||
//we had some problems with DynamicObjects, which pass radius = 0.0f (DB issue?)
|
||||
//maybe it is better to just return when radius <= 0.0f?
|
||||
if (radius <= 0.0f)
|
||||
{
|
||||
map.Visit(this, visitor);
|
||||
return;
|
||||
}
|
||||
//lets limit the upper value for search radius
|
||||
if (radius > MapConst.SizeofGrids)
|
||||
radius = MapConst.SizeofGrids;
|
||||
|
||||
//lets calculate object coord offsets from cell borders.
|
||||
CellArea area = CalculateCellArea(x_off, y_off, radius);
|
||||
//if radius fits inside standing cell
|
||||
if (area == null)
|
||||
{
|
||||
map.Visit(this, visitor);
|
||||
return;
|
||||
}
|
||||
|
||||
//visit all cells, found in CalculateCellArea()
|
||||
//if radius is known to reach cell area more than 4x4 then we should call optimized VisitCircle
|
||||
//currently this technique works with MAX_NUMBER_OF_CELLS 16 and higher, with lower values
|
||||
//there are nothing to optimize because SIZE_OF_GRID_CELL is too big...
|
||||
if ((area.high_bound.x_coord > (area.low_bound.x_coord + 4)) && (area.high_bound.y_coord > (area.low_bound.y_coord + 4)))
|
||||
{
|
||||
VisitCircle(visitor, map, area.low_bound, area.high_bound);
|
||||
return;
|
||||
}
|
||||
|
||||
//ALWAYS visit standing cell first!!! Since we deal with small radiuses
|
||||
//it is very essential to call visitor for standing cell firstly...
|
||||
map.Visit(this, visitor);
|
||||
|
||||
// loop the cell range
|
||||
for (uint x = area.low_bound.x_coord; x <= area.high_bound.x_coord; ++x)
|
||||
{
|
||||
for (uint y = area.low_bound.y_coord; y <= area.high_bound.y_coord; ++y)
|
||||
{
|
||||
CellCoord cellCoord = new CellCoord(x, y);
|
||||
//lets skip standing cell since we already visited it
|
||||
if (cellCoord != standing_cell)
|
||||
{
|
||||
Cell r_zone = new Cell(cellCoord);
|
||||
r_zone.data.nocreate = data.nocreate;
|
||||
map.Visit(r_zone, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VisitCircle(Visitor visitor, Map map, ICoord begin_cell, ICoord end_cell)
|
||||
{
|
||||
//here is an algorithm for 'filling' circum-squared octagon
|
||||
uint x_shift = (uint)Math.Ceiling((end_cell.x_coord - begin_cell.x_coord) * 0.3f - 0.5f);
|
||||
//lets calculate x_start/x_end coords for central strip...
|
||||
uint x_start = begin_cell.x_coord + x_shift;
|
||||
uint x_end = end_cell.x_coord - x_shift;
|
||||
|
||||
//visit central strip with constant width...
|
||||
for (uint x = x_start; x <= x_end; ++x)
|
||||
{
|
||||
for (uint y = begin_cell.y_coord; y <= end_cell.y_coord; ++y)
|
||||
{
|
||||
CellCoord cellCoord = new CellCoord(x, y);
|
||||
Cell r_zone = new Cell(cellCoord);
|
||||
r_zone.data.nocreate = data.nocreate;
|
||||
map.Visit(r_zone, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
//if x_shift == 0 then we have too small cell area, which were already
|
||||
//visited at previous step, so just return from procedure...
|
||||
if (x_shift == 0)
|
||||
return;
|
||||
|
||||
uint y_start = end_cell.y_coord;
|
||||
uint y_end = begin_cell.y_coord;
|
||||
//now we are visiting borders of an octagon...
|
||||
for (uint step = 1; step <= (x_start - begin_cell.x_coord); ++step)
|
||||
{
|
||||
//each step reduces strip height by 2 cells...
|
||||
y_end += 1;
|
||||
y_start -= 1;
|
||||
for (uint y = y_start; y >= y_end; --y)
|
||||
{
|
||||
//we visit cells symmetrically from both sides, heading from center to sides and from up to bottom
|
||||
//e.g. filling 2 trapezoids after filling central cell strip...
|
||||
CellCoord cellCoord_left = new CellCoord(x_start - step, y);
|
||||
Cell r_zone_left = new Cell(cellCoord_left);
|
||||
r_zone_left.data.nocreate = data.nocreate;
|
||||
map.Visit(r_zone_left, visitor);
|
||||
|
||||
//right trapezoid cell visit
|
||||
CellCoord cellCoord_right = new CellCoord(x_end + step, y);
|
||||
Cell r_zone_right = new Cell(cellCoord_right);
|
||||
r_zone_right.data.nocreate = data.nocreate;
|
||||
map.Visit(r_zone_right, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void VisitGridObjects(WorldObject center_obj, Notifier visitor, float radius, bool dont_load = true)
|
||||
{
|
||||
CellCoord p = GridDefines.ComputeCellCoord(center_obj.GetPositionX(), center_obj.GetPositionY());
|
||||
Cell cell = new Cell(p);
|
||||
if (dont_load)
|
||||
cell.SetNoCreate();
|
||||
|
||||
Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllGrid);
|
||||
cell.Visit(p, gnotifier, center_obj.GetMap(), center_obj, radius);
|
||||
}
|
||||
|
||||
public static void VisitWorldObjects(WorldObject center_obj, Notifier visitor, float radius, bool dont_load = true)
|
||||
{
|
||||
CellCoord p = GridDefines.ComputeCellCoord(center_obj.GetPositionX(), center_obj.GetPositionY());
|
||||
Cell cell = new Cell(p);
|
||||
if (dont_load)
|
||||
cell.SetNoCreate();
|
||||
|
||||
Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllWorld);
|
||||
cell.Visit(p, gnotifier, center_obj.GetMap(), center_obj, radius);
|
||||
}
|
||||
|
||||
public static void VisitAllObjects(WorldObject center_obj, Notifier visitor, float radius, bool dont_load = true)
|
||||
{
|
||||
CellCoord p = GridDefines.ComputeCellCoord(center_obj.GetPositionX(), center_obj.GetPositionY());
|
||||
Cell cell = new Cell(p);
|
||||
if (dont_load)
|
||||
cell.SetNoCreate();
|
||||
|
||||
Visitor wnotifier = new Visitor(visitor, GridMapTypeMask.AllWorld);
|
||||
cell.Visit(p, wnotifier, center_obj.GetMap(), center_obj, radius);
|
||||
Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllGrid);
|
||||
cell.Visit(p, gnotifier, center_obj.GetMap(), center_obj, radius);
|
||||
}
|
||||
|
||||
public static void VisitGridObjects(float x, float y, Map map, Notifier visitor, float radius, bool dont_load = true)
|
||||
{
|
||||
CellCoord p = GridDefines.ComputeCellCoord(x, y);
|
||||
Cell cell = new Cell(p);
|
||||
if (dont_load)
|
||||
cell.SetNoCreate();
|
||||
|
||||
Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllGrid);
|
||||
cell.Visit(p, gnotifier, map, x, y, radius);
|
||||
}
|
||||
|
||||
public static void VisitWorldObjects(float x, float y, Map map, Notifier visitor, float radius, bool dont_load = true)
|
||||
{
|
||||
CellCoord p = GridDefines.ComputeCellCoord(x, y);
|
||||
Cell cell = new Cell(p);
|
||||
if (dont_load)
|
||||
cell.SetNoCreate();
|
||||
|
||||
Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllWorld);
|
||||
cell.Visit(p, gnotifier, map, x, y, radius);
|
||||
}
|
||||
|
||||
public static void VisitAllObjects(float x, float y, Map map, Notifier visitor, float radius, bool dont_load = true)
|
||||
{
|
||||
CellCoord p = GridDefines.ComputeCellCoord(x, y);
|
||||
Cell cell = new Cell(p);
|
||||
if (dont_load)
|
||||
cell.SetNoCreate();
|
||||
|
||||
Visitor wnotifier = new Visitor(visitor, GridMapTypeMask.AllWorld);
|
||||
cell.Visit(p, wnotifier, map, x, y, radius);
|
||||
Visitor gnotifier = new Visitor(visitor, GridMapTypeMask.AllGrid);
|
||||
cell.Visit(p, gnotifier, map, x, y, radius);
|
||||
}
|
||||
|
||||
public static CellArea CalculateCellArea(float x, float y, float radius)
|
||||
{
|
||||
if (radius <= 0.0f)
|
||||
{
|
||||
CellCoord center = (CellCoord)GridDefines.ComputeCellCoord(x, y).normalize();
|
||||
return new CellArea(center, center);
|
||||
}
|
||||
|
||||
CellCoord centerX = (CellCoord)GridDefines.ComputeCellCoord(x - radius, y - radius).normalize();
|
||||
CellCoord centerY = (CellCoord)GridDefines.ComputeCellCoord(x + radius, y + radius).normalize();
|
||||
|
||||
return new CellArea(centerX, centerY);
|
||||
}
|
||||
}
|
||||
|
||||
public class CellArea
|
||||
{
|
||||
public CellArea() { }
|
||||
public CellArea(CellCoord low, CellCoord high)
|
||||
{
|
||||
low_bound = low;
|
||||
high_bound = high;
|
||||
}
|
||||
|
||||
void ResizeBorders(ref ICoord begin_cell, ref ICoord end_cell)
|
||||
{
|
||||
begin_cell = low_bound;
|
||||
end_cell = high_bound;
|
||||
}
|
||||
|
||||
public bool Check()
|
||||
{
|
||||
return low_bound == high_bound;
|
||||
}
|
||||
|
||||
public ICoord low_bound;
|
||||
public ICoord high_bound;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class GridInfo
|
||||
{
|
||||
public GridInfo()
|
||||
{
|
||||
i_timer = new TimeTracker(0);
|
||||
vis_Update = new PeriodicTimer(0, RandomHelper.IRand(0, 1000));
|
||||
i_unloadActiveLockCount = 0;
|
||||
i_unloadExplicitLock = false;
|
||||
i_unloadReferenceLock = false;
|
||||
}
|
||||
|
||||
public GridInfo(long expiry, bool unload = true)
|
||||
{
|
||||
i_timer = new TimeTracker((int)expiry);
|
||||
vis_Update = new PeriodicTimer(0, RandomHelper.IRand(0, 1000));
|
||||
i_unloadActiveLockCount = 0;
|
||||
i_unloadExplicitLock = !unload;
|
||||
i_unloadReferenceLock = false;
|
||||
}
|
||||
|
||||
public TimeTracker getTimeTracker()
|
||||
{
|
||||
return i_timer;
|
||||
}
|
||||
|
||||
public bool getUnloadLock()
|
||||
{
|
||||
return i_unloadActiveLockCount != 0 || i_unloadExplicitLock || i_unloadReferenceLock;
|
||||
}
|
||||
|
||||
public void setUnloadExplicitLock(bool on)
|
||||
{
|
||||
i_unloadExplicitLock = on;
|
||||
}
|
||||
|
||||
public void setUnloadReferenceLock(bool on)
|
||||
{
|
||||
i_unloadReferenceLock = on;
|
||||
}
|
||||
|
||||
public void incUnloadActiveLock()
|
||||
{
|
||||
++i_unloadActiveLockCount;
|
||||
}
|
||||
|
||||
public void decUnloadActiveLock()
|
||||
{
|
||||
if (i_unloadActiveLockCount != 0) --i_unloadActiveLockCount;
|
||||
}
|
||||
|
||||
private void setTimer(TimeTracker pTimer)
|
||||
{
|
||||
i_timer = pTimer;
|
||||
}
|
||||
|
||||
public void ResetTimeTracker(long interval)
|
||||
{
|
||||
i_timer.Reset(interval);
|
||||
}
|
||||
|
||||
public void UpdateTimeTracker(long diff)
|
||||
{
|
||||
i_timer.Update(diff);
|
||||
}
|
||||
|
||||
public PeriodicTimer getRelocationTimer()
|
||||
{
|
||||
return vis_Update;
|
||||
}
|
||||
|
||||
TimeTracker i_timer;
|
||||
PeriodicTimer vis_Update;
|
||||
|
||||
ushort i_unloadActiveLockCount; // lock from active object spawn points (prevent clone loading)
|
||||
bool i_unloadExplicitLock; // explicit manual lock or config setting
|
||||
bool i_unloadReferenceLock; // lock from instance map copy
|
||||
}
|
||||
|
||||
public class Grid
|
||||
{
|
||||
public Grid(uint id, uint x, uint y, long expiry, bool unload = true)
|
||||
{
|
||||
gridId = id;
|
||||
gridX = x;
|
||||
gridY = y;
|
||||
gridInfo = new GridInfo(expiry, unload);
|
||||
gridState = GridState.Invalid;
|
||||
gridObjectDataLoaded = false;
|
||||
|
||||
for (uint xx = 0; xx < MapConst.MaxCells; ++xx)
|
||||
{
|
||||
i_cells[xx] = new GridCell[MapConst.MaxCells];
|
||||
for (uint yy = 0; yy < MapConst.MaxCells; ++yy)
|
||||
i_cells[xx][yy] = new GridCell();
|
||||
}
|
||||
}
|
||||
|
||||
public Grid(Cell cell, uint expiry, bool unload = true) : this(cell.GetId(), cell.GetGridX(), cell.GetGridY(), expiry, unload) { }
|
||||
|
||||
public GridCell GetGridCell(uint x, uint y)
|
||||
{
|
||||
return i_cells[x][y];
|
||||
}
|
||||
|
||||
public uint GetGridId()
|
||||
{
|
||||
return gridId;
|
||||
}
|
||||
|
||||
private void SetGridId(uint id)
|
||||
{
|
||||
gridId = id;
|
||||
}
|
||||
|
||||
public GridState GetGridState()
|
||||
{
|
||||
return gridState;
|
||||
}
|
||||
|
||||
public void SetGridState(GridState s)
|
||||
{
|
||||
gridState = s;
|
||||
}
|
||||
|
||||
public uint getX()
|
||||
{
|
||||
return gridX;
|
||||
}
|
||||
|
||||
public uint getY()
|
||||
{
|
||||
return gridY;
|
||||
}
|
||||
|
||||
public bool isGridObjectDataLoaded()
|
||||
{
|
||||
return gridObjectDataLoaded;
|
||||
}
|
||||
|
||||
public void setGridObjectDataLoaded(bool pLoaded)
|
||||
{
|
||||
gridObjectDataLoaded = pLoaded;
|
||||
}
|
||||
|
||||
public GridInfo getGridInfoRef()
|
||||
{
|
||||
return gridInfo;
|
||||
}
|
||||
|
||||
private TimeTracker getTimeTracker()
|
||||
{
|
||||
return gridInfo.getTimeTracker();
|
||||
}
|
||||
|
||||
public bool getUnloadLock()
|
||||
{
|
||||
return gridInfo.getUnloadLock();
|
||||
}
|
||||
|
||||
public void setUnloadExplicitLock(bool on)
|
||||
{
|
||||
gridInfo.setUnloadExplicitLock(on);
|
||||
}
|
||||
|
||||
public void setUnloadReferenceLock(bool on)
|
||||
{
|
||||
gridInfo.setUnloadReferenceLock(on);
|
||||
}
|
||||
|
||||
public void incUnloadActiveLock()
|
||||
{
|
||||
gridInfo.incUnloadActiveLock();
|
||||
}
|
||||
|
||||
public void decUnloadActiveLock()
|
||||
{
|
||||
gridInfo.decUnloadActiveLock();
|
||||
}
|
||||
|
||||
public void ResetTimeTracker(long interval)
|
||||
{
|
||||
gridInfo.ResetTimeTracker(interval);
|
||||
}
|
||||
|
||||
public void UpdateTimeTracker(long diff)
|
||||
{
|
||||
gridInfo.UpdateTimeTracker(diff);
|
||||
}
|
||||
|
||||
public void Update(Map m, uint diff)
|
||||
{
|
||||
switch (GetGridState())
|
||||
{
|
||||
case GridState.Active:
|
||||
// Only check grid activity every (grid_expiry/10) ms, because it's really useless to do it every cycle
|
||||
getGridInfoRef().UpdateTimeTracker(diff);
|
||||
if (getGridInfoRef().getTimeTracker().Passed())
|
||||
{
|
||||
if (GetWorldObjectCountInNGrid<Player>() == 0 && !m.ActiveObjectsNearGrid(this))
|
||||
{
|
||||
ObjectGridStoper worker = new ObjectGridStoper();
|
||||
var visitor = new Visitor(worker, GridMapTypeMask.AllGrid);
|
||||
VisitAllGrids(visitor);
|
||||
SetGridState(GridState.Idle);
|
||||
Log.outDebug(LogFilter.Maps, "Grid[{0}, {1}] on map {2} moved to IDLE state", getX(), getY(),
|
||||
m.GetId());
|
||||
}
|
||||
else
|
||||
m.ResetGridExpiry(this, 0.1f);
|
||||
}
|
||||
break;
|
||||
case GridState.Idle:
|
||||
m.ResetGridExpiry(this);
|
||||
SetGridState(GridState.Removal);
|
||||
Log.outDebug(LogFilter.Maps, "Grid[{0}, {1}] on map {2} moved to REMOVAL state", getX(), getY(),
|
||||
m.GetId());
|
||||
break;
|
||||
case GridState.Removal:
|
||||
if (!getGridInfoRef().getUnloadLock())
|
||||
{
|
||||
getGridInfoRef().UpdateTimeTracker(diff);
|
||||
if (getGridInfoRef().getTimeTracker().Passed())
|
||||
{
|
||||
if (!m.UnloadGrid(this, false))
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps,
|
||||
"Grid[{0}, {1}] for map {2} differed unloading due to players or active objects nearby",
|
||||
getX(), getY(), m.GetId());
|
||||
m.ResetGridExpiry(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void VisitAllGrids(Visitor visitor)
|
||||
{
|
||||
for (uint x = 0; x < MapConst.MaxCells; ++x)
|
||||
for (uint y = 0; y < MapConst.MaxCells; ++y)
|
||||
GetGridCell(x, y).Visit(visitor);
|
||||
}
|
||||
|
||||
public void VisitGrid(uint x, uint y, Visitor visitor)
|
||||
{
|
||||
GetGridCell(x, y).Visit(visitor);
|
||||
}
|
||||
|
||||
public uint GetWorldObjectCountInNGrid<T>() where T : WorldObject
|
||||
{
|
||||
uint count = 0;
|
||||
for (uint x = 0; x < MapConst.MaxCells; ++x)
|
||||
for (uint y = 0; y < MapConst.MaxCells; ++y)
|
||||
count += i_cells[x][y].GetWorldObjectCountInGrid<T>();
|
||||
return count;
|
||||
}
|
||||
|
||||
uint gridId;
|
||||
uint gridX;
|
||||
uint gridY;
|
||||
GridInfo gridInfo;
|
||||
GridState gridState;
|
||||
bool gridObjectDataLoaded;
|
||||
GridCell[][] i_cells = new GridCell[MapConst.MaxCells][];
|
||||
}
|
||||
|
||||
public class GridCell
|
||||
{
|
||||
public GridCell()
|
||||
{
|
||||
_objects = new MultiTypeContainer();
|
||||
_container = new MultiTypeContainer();
|
||||
}
|
||||
|
||||
public void Visit(Visitor visitor)
|
||||
{
|
||||
switch (visitor._mask)
|
||||
{
|
||||
case GridMapTypeMask.AllGrid:
|
||||
visitor.Visit(_container.gameObjects.ToList());
|
||||
visitor.Visit(_container.creatures.ToList());
|
||||
visitor.Visit(_container.dynamicObjects.ToList());
|
||||
visitor.Visit(_container.corpses.ToList());
|
||||
visitor.Visit(_container.areaTriggers.ToList());
|
||||
visitor.Visit(_container.conversations.ToList());
|
||||
visitor.Visit(_container.worldObjects.ToList());
|
||||
break;
|
||||
case GridMapTypeMask.AllWorld:
|
||||
visitor.Visit(_objects.players);
|
||||
visitor.Visit(_objects.creatures.ToList());
|
||||
visitor.Visit(_objects.corpses.ToList());
|
||||
visitor.Visit(_objects.dynamicObjects.ToList());
|
||||
visitor.Visit(_objects.worldObjects.ToList());
|
||||
break;
|
||||
default:
|
||||
Log.outError(LogFilter.Server, "{0} called Visit with Unknown Mask {1}.", visitor.ToString(), visitor._mask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetWorldObjectCountInGrid<T>() where T : WorldObject
|
||||
{
|
||||
return (uint)_objects.GetCount<T>();
|
||||
}
|
||||
|
||||
public void AddWorldObject(WorldObject obj)
|
||||
{
|
||||
_objects.Insert(obj);
|
||||
}
|
||||
|
||||
public void AddGridObject(WorldObject obj)
|
||||
{
|
||||
_container.Insert(obj);
|
||||
}
|
||||
|
||||
public void RemoveWorldObject(WorldObject obj)
|
||||
{
|
||||
_objects.Remove(obj);
|
||||
}
|
||||
|
||||
public void RemoveGridObject(WorldObject obj)
|
||||
{
|
||||
_container.Remove(obj);
|
||||
}
|
||||
|
||||
public bool HasWorldObject(WorldObject obj)
|
||||
{
|
||||
return _objects.Contains(obj);
|
||||
}
|
||||
|
||||
public bool HasGridObject(WorldObject obj)
|
||||
{
|
||||
return _container.Contains(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds all World objects - Player, Pets, Corpse(resurrectable), DynamicObject(farsight)
|
||||
/// </summary>
|
||||
MultiTypeContainer _objects;
|
||||
|
||||
/// <summary>
|
||||
/// Holds all Grid objects - GameObjects, Creatures(except pets), DynamicObject, Corpse(Bones), AreaTrigger, Conversation
|
||||
/// </summary>
|
||||
MultiTypeContainer _container;
|
||||
}
|
||||
|
||||
public class MultiTypeContainer
|
||||
{
|
||||
public void Insert(WorldObject obj)
|
||||
{
|
||||
worldObjects.Add(obj);
|
||||
switch (obj.GetTypeId())
|
||||
{
|
||||
case TypeId.Unit:
|
||||
creatures.Add((Creature)obj);
|
||||
break;
|
||||
case TypeId.Player:
|
||||
players.Add((Player)obj);
|
||||
break;
|
||||
case TypeId.GameObject:
|
||||
gameObjects.Add((GameObject)obj);
|
||||
break;
|
||||
case TypeId.DynamicObject:
|
||||
dynamicObjects.Add((DynamicObject)obj);
|
||||
break;
|
||||
case TypeId.Corpse:
|
||||
corpses.Add((Corpse)obj);
|
||||
break;
|
||||
case TypeId.AreaTrigger:
|
||||
areaTriggers.Add((AreaTrigger)obj);
|
||||
break;
|
||||
case TypeId.Conversation:
|
||||
conversations.Add((Conversation)obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(WorldObject obj)
|
||||
{
|
||||
worldObjects.Remove(obj);
|
||||
switch (obj.GetTypeId())
|
||||
{
|
||||
case TypeId.Unit:
|
||||
creatures.Remove((Creature)obj);
|
||||
break;
|
||||
case TypeId.Player:
|
||||
players.Remove((Player)obj);
|
||||
break;
|
||||
case TypeId.GameObject:
|
||||
gameObjects.Remove((GameObject)obj);
|
||||
break;
|
||||
case TypeId.DynamicObject:
|
||||
dynamicObjects.Remove((DynamicObject)obj);
|
||||
break;
|
||||
case TypeId.Corpse:
|
||||
corpses.Remove((Corpse)obj);
|
||||
break;
|
||||
case TypeId.AreaTrigger:
|
||||
areaTriggers.Remove((AreaTrigger)obj);
|
||||
break;
|
||||
case TypeId.Conversation:
|
||||
conversations.Remove((Conversation)obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(WorldObject obj)
|
||||
{
|
||||
return worldObjects.Contains(obj);
|
||||
}
|
||||
|
||||
public int GetCount<T>()
|
||||
{
|
||||
switch (typeof(T).Name)
|
||||
{
|
||||
case "Creature":
|
||||
return creatures.Count;
|
||||
case "Player":
|
||||
return players.Count;
|
||||
case "GameObject":
|
||||
return gameObjects.Count;
|
||||
case "DynamicObject":
|
||||
return dynamicObjects.Count;
|
||||
case "Corpse":
|
||||
return corpses.Count;
|
||||
case "AreaTrigger":
|
||||
return areaTriggers.Count;
|
||||
case "Conversation":
|
||||
return conversations.Count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public List<Player> players = new List<Player>();
|
||||
public List<Creature> creatures = new List<Creature>();
|
||||
public List<Corpse> corpses = new List<Corpse>();
|
||||
public List<DynamicObject> dynamicObjects = new List<DynamicObject>();
|
||||
public List<AreaTrigger> areaTriggers = new List<AreaTrigger>();
|
||||
public List<Conversation> conversations = new List<Conversation>();
|
||||
public List<GameObject> gameObjects = new List<GameObject>();
|
||||
public List<WorldObject> worldObjects = new List<WorldObject>();
|
||||
}
|
||||
|
||||
public enum GridState
|
||||
{
|
||||
Invalid = 0,
|
||||
Active = 1,
|
||||
Idle = 2,
|
||||
Removal = 3,
|
||||
Max = 4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Entities;
|
||||
using System;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class GridDefines
|
||||
{
|
||||
public static bool IsValidMapCoord(float c)
|
||||
{
|
||||
return !float.IsInfinity(c) && (Math.Abs(c) <= (MapConst.MapHalfSize - 0.5f));
|
||||
}
|
||||
|
||||
public static bool IsValidMapCoord(float x, float y)
|
||||
{
|
||||
return (IsValidMapCoord(x) && IsValidMapCoord(y));
|
||||
}
|
||||
|
||||
public static bool IsValidMapCoord(float x, float y, float z)
|
||||
{
|
||||
return IsValidMapCoord(x, y) && IsValidMapCoord(z);
|
||||
}
|
||||
|
||||
public static bool IsValidMapCoord(float x, float y, float z, float o)
|
||||
{
|
||||
return IsValidMapCoord(x, y, z) && !float.IsInfinity(o);
|
||||
}
|
||||
|
||||
public static bool IsValidMapCoord(uint mapid, float x, float y)
|
||||
{
|
||||
return Global.MapMgr.IsValidMAP(mapid, false) && IsValidMapCoord(x, y);
|
||||
}
|
||||
|
||||
public static bool IsValidMapCoord(uint mapid, float x, float y, float z)
|
||||
{
|
||||
return Global.MapMgr.IsValidMAP(mapid, false) && IsValidMapCoord(x, y, z);
|
||||
}
|
||||
|
||||
public static bool IsValidMapCoord(uint mapid, float x, float y, float z, float o)
|
||||
{
|
||||
return Global.MapMgr.IsValidMAP(mapid, false) && IsValidMapCoord(x, y, z, o);
|
||||
}
|
||||
|
||||
public static bool IsValidMapCoord(WorldLocation loc)
|
||||
{
|
||||
return IsValidMapCoord(loc.GetMapId(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), loc.GetOrientation());
|
||||
}
|
||||
|
||||
public static void NormalizeMapCoord(ref float c)
|
||||
{
|
||||
if (c > MapConst.MapHalfSize - 0.5f)
|
||||
c = MapConst.MapHalfSize - 0.5f;
|
||||
else if (c < -(MapConst.MapHalfSize - 0.5f))
|
||||
c = -(MapConst.MapHalfSize - 0.5f);
|
||||
}
|
||||
|
||||
public static GridCoord ComputeGridCoord(float x, float y)
|
||||
{
|
||||
double x_offset = ((double)x - MapConst.CenterGridOffset) / MapConst.SizeofGrids;
|
||||
double y_offset = ((double)y - MapConst.CenterGridOffset) / MapConst.SizeofGrids;
|
||||
|
||||
uint x_val = (uint)(x_offset + MapConst.CenterGridId + 0.5f);
|
||||
uint y_val = (uint)(y_offset + MapConst.CenterGridId + 0.5f);
|
||||
return new GridCoord(x_val, y_val);
|
||||
}
|
||||
|
||||
public static CellCoord ComputeCellCoord(float x, float y)
|
||||
{
|
||||
double x_offset = ((double)x - MapConst.CenterGridCellOffset) / MapConst.SizeofCells;
|
||||
double y_offset = ((double)y - MapConst.CenterGridCellOffset) / MapConst.SizeofCells;
|
||||
|
||||
uint x_val = (uint)(x_offset + MapConst.CenterGridCellId + 0.5f);
|
||||
uint y_val = (uint)(y_offset + MapConst.CenterGridCellId + 0.5f);
|
||||
return new CellCoord(x_val, y_val);
|
||||
}
|
||||
}
|
||||
|
||||
public class CellCoord : ICoord
|
||||
{
|
||||
const int Limit = MapConst.TotalCellsPerMap;
|
||||
|
||||
public CellCoord(uint x, uint y)
|
||||
{
|
||||
x_coord = x;
|
||||
y_coord = y;
|
||||
}
|
||||
public CellCoord(CellCoord obj)
|
||||
{
|
||||
x_coord = obj.x_coord;
|
||||
y_coord = obj.y_coord;
|
||||
}
|
||||
|
||||
public bool IsCoordValid()
|
||||
{
|
||||
return x_coord < Limit && y_coord < Limit;
|
||||
}
|
||||
public ICoord normalize()
|
||||
{
|
||||
x_coord = Math.Min(x_coord, Limit - 1);
|
||||
y_coord = Math.Min(y_coord, Limit - 1);
|
||||
return this;
|
||||
}
|
||||
public uint GetId()
|
||||
{
|
||||
return y_coord * Limit + x_coord;
|
||||
}
|
||||
|
||||
public void dec_x(uint val)
|
||||
{
|
||||
if (x_coord > val)
|
||||
x_coord -= val;
|
||||
else
|
||||
x_coord = 0;
|
||||
}
|
||||
public void inc_x(uint val)
|
||||
{
|
||||
if (x_coord + val < Limit)
|
||||
x_coord += val;
|
||||
else
|
||||
x_coord = Limit - 1;
|
||||
}
|
||||
|
||||
public void dec_y(uint val)
|
||||
{
|
||||
if (y_coord > val)
|
||||
y_coord -= val;
|
||||
else
|
||||
y_coord = 0;
|
||||
}
|
||||
public void inc_y(uint val)
|
||||
{
|
||||
if (y_coord + val < Limit)
|
||||
y_coord += val;
|
||||
else
|
||||
y_coord = Limit - 1;
|
||||
}
|
||||
|
||||
public static bool operator ==(CellCoord p1, CellCoord p2)
|
||||
{
|
||||
return (p1.x_coord == p2.x_coord && p1.y_coord == p2.y_coord);
|
||||
}
|
||||
public static bool operator !=(CellCoord p1, CellCoord p2)
|
||||
{
|
||||
return !(p1 == p2);
|
||||
}
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public uint x_coord { get; set; }
|
||||
public uint y_coord { get; set; }
|
||||
}
|
||||
|
||||
public class GridCoord : ICoord
|
||||
{
|
||||
const int Limit = MapConst.MaxGrids;
|
||||
|
||||
public GridCoord(uint x, uint y)
|
||||
{
|
||||
x_coord = x;
|
||||
y_coord = y;
|
||||
}
|
||||
public GridCoord(GridCoord obj)
|
||||
{
|
||||
x_coord = obj.x_coord;
|
||||
y_coord = obj.y_coord;
|
||||
}
|
||||
|
||||
public bool IsCoordValid()
|
||||
{
|
||||
return x_coord < Limit && y_coord < Limit;
|
||||
}
|
||||
public ICoord normalize()
|
||||
{
|
||||
x_coord = Math.Min(x_coord, Limit - 1);
|
||||
y_coord = Math.Min(y_coord, Limit - 1);
|
||||
return this;
|
||||
}
|
||||
public uint GetId()
|
||||
{
|
||||
return y_coord * Limit + x_coord;
|
||||
}
|
||||
|
||||
public void dec_x(uint val)
|
||||
{
|
||||
if (x_coord > val)
|
||||
x_coord -= val;
|
||||
else
|
||||
x_coord = 0;
|
||||
}
|
||||
public void inc_x(uint val)
|
||||
{
|
||||
if (x_coord + val < Limit)
|
||||
x_coord += val;
|
||||
else
|
||||
x_coord = Limit - 1;
|
||||
}
|
||||
|
||||
public void dec_y(uint val)
|
||||
{
|
||||
if (y_coord > val)
|
||||
y_coord -= val;
|
||||
else
|
||||
y_coord = 0;
|
||||
}
|
||||
public void inc_y(uint val)
|
||||
{
|
||||
if (y_coord + val < Limit)
|
||||
y_coord += val;
|
||||
else
|
||||
y_coord = Limit - 1;
|
||||
}
|
||||
|
||||
public static bool operator ==(GridCoord first, GridCoord other)
|
||||
{
|
||||
if (ReferenceEquals(first, other))
|
||||
return true;
|
||||
|
||||
if ((object)first == null || (object)other == null)
|
||||
return false;
|
||||
|
||||
return first.Equals(other);
|
||||
}
|
||||
|
||||
public static bool operator !=(GridCoord first, GridCoord other)
|
||||
{
|
||||
return !(first == other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj != null && obj is ObjectGuid && Equals((ObjectGuid)obj);
|
||||
}
|
||||
|
||||
public bool Equals(GridCoord other)
|
||||
{
|
||||
return other.x_coord == x_coord && other.y_coord == y_coord;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return new { x_coord, y_coord }.GetHashCode();
|
||||
}
|
||||
|
||||
|
||||
public uint x_coord { get; set; }
|
||||
public uint y_coord { get; set; }
|
||||
}
|
||||
|
||||
public interface ICoord
|
||||
{
|
||||
bool IsCoordValid();
|
||||
ICoord normalize();
|
||||
uint GetId();
|
||||
void dec_x(uint val);
|
||||
void inc_x(uint val);
|
||||
void dec_y(uint val);
|
||||
void inc_y(uint val);
|
||||
|
||||
uint x_coord { get; set; }
|
||||
uint y_coord { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.GameMath;
|
||||
using Game.DataStorage;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class GridMap
|
||||
{
|
||||
public GridMap()
|
||||
{
|
||||
// Height level data
|
||||
_gridHeight = MapConst.InvalidHeight;
|
||||
_gridGetHeight = getHeightFromFlat;
|
||||
|
||||
// Liquid data
|
||||
_liquidLevel = MapConst.InvalidHeight;
|
||||
}
|
||||
|
||||
public bool loadData(string filename)
|
||||
{
|
||||
unloadData();
|
||||
if (!File.Exists(filename))
|
||||
return true;
|
||||
|
||||
using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
|
||||
{
|
||||
mapFileHeader header = reader.ReadStruct<mapFileHeader>();
|
||||
if (new string(header.mapMagic) != MapConst.MapMagic || new string(header.versionMagic) != MapConst.MapVersionMagic)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "Map file '{0}' is from an incompatible map version. Please recreate using the mapextractor.", filename);
|
||||
return false;
|
||||
}
|
||||
if (header.areaMapOffset != 0)
|
||||
LoadAreaData(reader, header.areaMapOffset);
|
||||
|
||||
if (header.heightMapOffset != 0)
|
||||
LoadHeightData(reader, header.heightMapOffset);
|
||||
|
||||
if (header.liquidMapOffset != 0)
|
||||
LoadLiquidData(reader, header.liquidMapOffset);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void unloadData()
|
||||
{
|
||||
_areaMap = null;
|
||||
m_V9 = null;
|
||||
m_V8 = null;
|
||||
_liquidEntry = null;
|
||||
_liquidFlags = null;
|
||||
_liquidMap = null;
|
||||
_gridGetHeight = getHeightFromFlat;
|
||||
}
|
||||
|
||||
void LoadAreaData(BinaryReader reader, uint offset)
|
||||
{
|
||||
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
|
||||
map_AreaHeader areaHeader = reader.ReadStruct<map_AreaHeader>();
|
||||
|
||||
_gridArea = areaHeader.gridArea;
|
||||
|
||||
if (!areaHeader.flags.HasAnyFlag(AreaHeaderFlags.NoArea))
|
||||
{
|
||||
_areaMap = new ushort[16 * 16];
|
||||
for (var i = 0; i < _areaMap.Length; ++i)
|
||||
_areaMap[i] = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
|
||||
void LoadHeightData(BinaryReader reader, uint offset)
|
||||
{
|
||||
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
|
||||
map_HeightHeader mapHeader = reader.ReadStruct<map_HeightHeader>();
|
||||
|
||||
_gridHeight = mapHeader.gridHeight;
|
||||
_flags = (uint)mapHeader.flags;
|
||||
|
||||
if (!mapHeader.flags.HasAnyFlag(HeightHeaderFlags.NoHeight))
|
||||
{
|
||||
if (mapHeader.flags.HasAnyFlag(HeightHeaderFlags.HeightAsInt16))
|
||||
{
|
||||
m_uint16_V9 = new ushort[129 * 129];
|
||||
for (var i = 0; i < m_uint16_V9.Length; ++i)
|
||||
m_uint16_V9[i] = reader.ReadUInt16();
|
||||
|
||||
m_uint16_V8 = new ushort[128 * 128];
|
||||
for (var i = 0; i < m_uint16_V8.Length; ++i)
|
||||
m_uint16_V8[i] = reader.ReadUInt16();
|
||||
|
||||
_gridIntHeightMultiplier = (mapHeader.gridMaxHeight - mapHeader.gridHeight) / 65535;
|
||||
_gridGetHeight = getHeightFromUint16;
|
||||
}
|
||||
else if (mapHeader.flags.HasAnyFlag(HeightHeaderFlags.HeightAsInt8))
|
||||
{
|
||||
m_ubyte_V9 = reader.ReadBytes(129 * 129);
|
||||
m_ubyte_V8 = reader.ReadBytes(128 * 128);
|
||||
_gridIntHeightMultiplier = (mapHeader.gridMaxHeight - mapHeader.gridHeight) / 255;
|
||||
_gridGetHeight = getHeightFromUint8;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_V9 = new float[129 * 129];
|
||||
for (var i = 0; i < m_V9.Length; ++i)
|
||||
m_V9[i] = reader.ReadSingle();
|
||||
|
||||
m_V8 = new float[128 * 128];
|
||||
for (var i = 0; i < m_V8.Length; ++i)
|
||||
m_V8[i] = reader.ReadSingle();
|
||||
|
||||
_gridGetHeight = getHeightFromFloat;
|
||||
}
|
||||
}
|
||||
else
|
||||
_gridGetHeight = getHeightFromFlat;
|
||||
|
||||
if (mapHeader.flags.HasAnyFlag(HeightHeaderFlags.HeightHasFlightBounds))
|
||||
{
|
||||
_maxHeight = new short[3 * 3];
|
||||
for (var i = 0; i < _maxHeight.Length; ++i)
|
||||
_maxHeight[i] = reader.ReadInt16();
|
||||
|
||||
_minHeight = new short[3 * 3];
|
||||
for (var i = 0; i < _minHeight.Length; ++i)
|
||||
_minHeight[i] = reader.ReadInt16();
|
||||
}
|
||||
}
|
||||
|
||||
void LoadLiquidData(BinaryReader reader, uint offset)
|
||||
{
|
||||
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
|
||||
map_LiquidHeader liquidHeader = reader.ReadStruct<map_LiquidHeader>();
|
||||
|
||||
_liquidType = liquidHeader.liquidType;
|
||||
_liquidOffX = liquidHeader.offsetX;
|
||||
_liquidOffY = liquidHeader.offsetY;
|
||||
_liquidWidth = liquidHeader.width;
|
||||
_liquidHeight = liquidHeader.height;
|
||||
_liquidLevel = liquidHeader.liquidLevel;
|
||||
|
||||
if (!liquidHeader.flags.HasAnyFlag(LiquidHeaderFlags.LiquidNoType))
|
||||
{
|
||||
_liquidEntry = new ushort[16 * 16];
|
||||
for (var i = 0; i < _liquidEntry.Length; ++i)
|
||||
_liquidEntry[i] = reader.ReadUInt16();
|
||||
|
||||
_liquidFlags = reader.ReadBytes(16 * 16);
|
||||
}
|
||||
|
||||
if (!liquidHeader.flags.HasAnyFlag(LiquidHeaderFlags.LiquidNoHeight))
|
||||
{
|
||||
_liquidMap = new float[_liquidWidth * _liquidHeight];
|
||||
for (var i = 0; i < _liquidMap.Length; ++i)
|
||||
_liquidMap[i] = reader.ReadSingle();
|
||||
}
|
||||
}
|
||||
|
||||
public ushort getArea(float x, float y)
|
||||
{
|
||||
if (_areaMap == null)
|
||||
return _gridArea;
|
||||
|
||||
x = 16 * (32 - x / MapConst.SizeofGrids);
|
||||
y = 16 * (32 - y / MapConst.SizeofGrids);
|
||||
int lx = (int)x & 15;
|
||||
int ly = (int)y & 15;
|
||||
return _areaMap[lx * 16 + ly];
|
||||
}
|
||||
|
||||
float getHeightFromFlat(float x, float y)
|
||||
{
|
||||
return _gridHeight;
|
||||
}
|
||||
|
||||
float getHeightFromFloat(float x, float y)
|
||||
{
|
||||
if (m_uint16_V8 == null || m_uint16_V9 == null)
|
||||
return _gridHeight;
|
||||
|
||||
x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
|
||||
y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
|
||||
|
||||
int x_int = (int)x;
|
||||
int y_int = (int)y;
|
||||
x -= x_int;
|
||||
y -= y_int;
|
||||
x_int &= (MapConst.MapResolution - 1);
|
||||
y_int &= (MapConst.MapResolution - 1);
|
||||
|
||||
float a, b, c;
|
||||
if (x + y < 1)
|
||||
{
|
||||
if (x > y)
|
||||
{
|
||||
// 1 triangle (h1, h2, h5 points)
|
||||
float h1 = m_V9[(x_int) * 129 + y_int];
|
||||
float h2 = m_V9[(x_int + 1) * 129 + y_int];
|
||||
float h5 = 2 * m_V8[x_int * 128 + y_int];
|
||||
a = h2 - h1;
|
||||
b = h5 - h1 - h2;
|
||||
c = h1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2 triangle (h1, h3, h5 points)
|
||||
float h1 = m_V9[x_int * 129 + y_int];
|
||||
float h3 = m_V9[x_int * 129 + y_int + 1];
|
||||
float h5 = 2 * m_V8[x_int * 128 + y_int];
|
||||
a = h5 - h1 - h3;
|
||||
b = h3 - h1;
|
||||
c = h1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x > y)
|
||||
{
|
||||
// 3 triangle (h2, h4, h5 points)
|
||||
float h2 = m_V9[(x_int + 1) * 129 + y_int];
|
||||
float h4 = m_V9[(x_int + 1) * 129 + y_int + 1];
|
||||
float h5 = 2 * m_V8[x_int * 128 + y_int];
|
||||
a = h2 + h4 - h5;
|
||||
b = h4 - h2;
|
||||
c = h5 - h4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 4 triangle (h3, h4, h5 points)
|
||||
float h3 = m_V9[(x_int) * 129 + y_int + 1];
|
||||
float h4 = m_V9[(x_int + 1) * 129 + y_int + 1];
|
||||
float h5 = 2 * m_V8[x_int * 128 + y_int];
|
||||
a = h4 - h3;
|
||||
b = h3 + h4 - h5;
|
||||
c = h5 - h4;
|
||||
}
|
||||
}
|
||||
// Calculate height
|
||||
return a * x + b * y + c;
|
||||
}
|
||||
|
||||
float getHeightFromUint8(float x, float y)
|
||||
{
|
||||
if (m_ubyte_V8 == null || m_ubyte_V9 == null)
|
||||
return _gridHeight;
|
||||
|
||||
x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
|
||||
y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
|
||||
|
||||
int x_int = (int)x;
|
||||
int y_int = (int)y;
|
||||
x -= x_int;
|
||||
y -= y_int;
|
||||
x_int &= (MapConst.MapResolution - 1);
|
||||
y_int &= (MapConst.MapResolution - 1);
|
||||
int a, b, c;
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* V9 = m_ubyte_V9)
|
||||
{
|
||||
byte* V9_h1_ptr = &V9[x_int * 128 + x_int + y_int];
|
||||
if (x + y < 1)
|
||||
{
|
||||
if (x > y)
|
||||
{
|
||||
// 1 triangle (h1, h2, h5 points)
|
||||
int h1 = V9_h1_ptr[0];
|
||||
int h2 = V9_h1_ptr[129];
|
||||
int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int];
|
||||
a = h2 - h1;
|
||||
b = h5 - h1 - h2;
|
||||
c = h1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2 triangle (h1, h3, h5 points)
|
||||
int h1 = V9_h1_ptr[0];
|
||||
int h3 = V9_h1_ptr[1];
|
||||
int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int];
|
||||
a = h5 - h1 - h3;
|
||||
b = h3 - h1;
|
||||
c = h1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x > y)
|
||||
{
|
||||
// 3 triangle (h2, h4, h5 points)
|
||||
int h2 = V9_h1_ptr[129];
|
||||
int h4 = V9_h1_ptr[130];
|
||||
int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int];
|
||||
a = h2 + h4 - h5;
|
||||
b = h4 - h2;
|
||||
c = h5 - h4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 4 triangle (h3, h4, h5 points)
|
||||
int h3 = V9_h1_ptr[1];
|
||||
int h4 = V9_h1_ptr[130];
|
||||
int h5 = 2 * m_ubyte_V8[x_int * 128 + y_int];
|
||||
a = h4 - h3;
|
||||
b = h3 + h4 - h5;
|
||||
c = h5 - h4;
|
||||
}
|
||||
}
|
||||
// Calculate height
|
||||
return ((a * x) + (b * y) + c) * _gridIntHeightMultiplier + _gridHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float getHeightFromUint16(float x, float y)
|
||||
{
|
||||
if (m_uint16_V8 == null || m_uint16_V9 == null)
|
||||
return _gridHeight;
|
||||
|
||||
x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
|
||||
y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
|
||||
|
||||
int x_int = (int)x;
|
||||
int y_int = (int)y;
|
||||
x -= x_int;
|
||||
y -= y_int;
|
||||
x_int &= (MapConst.MapResolution - 1);
|
||||
y_int &= (MapConst.MapResolution - 1);
|
||||
int a, b, c;
|
||||
unsafe
|
||||
{
|
||||
fixed (ushort* V9 = m_uint16_V9)
|
||||
{
|
||||
ushort* V9_h1_ptr = &V9[x_int * 128 + x_int + y_int];
|
||||
if (x + y < 1)
|
||||
{
|
||||
if (x > y)
|
||||
{
|
||||
// 1 triangle (h1, h2, h5 points)
|
||||
int h1 = V9_h1_ptr[0];
|
||||
int h2 = V9_h1_ptr[129];
|
||||
int h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
|
||||
a = h2 - h1;
|
||||
b = h5 - h1 - h2;
|
||||
c = h1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2 triangle (h1, h3, h5 points)
|
||||
int h1 = V9_h1_ptr[0];
|
||||
int h3 = V9_h1_ptr[1];
|
||||
int h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
|
||||
a = h5 - h1 - h3;
|
||||
b = h3 - h1;
|
||||
c = h1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x > y)
|
||||
{
|
||||
// 3 triangle (h2, h4, h5 points)
|
||||
int h2 = V9_h1_ptr[129];
|
||||
int h4 = V9_h1_ptr[130];
|
||||
int h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
|
||||
a = h2 + h4 - h5;
|
||||
b = h4 - h2;
|
||||
c = h5 - h4;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 4 triangle (h3, h4, h5 points)
|
||||
int h3 = V9_h1_ptr[1];
|
||||
int h4 = V9_h1_ptr[130];
|
||||
int h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
|
||||
a = h4 - h3;
|
||||
b = h3 + h4 - h5;
|
||||
c = h5 - h4;
|
||||
}
|
||||
}
|
||||
// Calculate height
|
||||
return ((a * x) + (b * y) + c) * _gridIntHeightMultiplier + _gridHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float getMinHeight(float x, float y)
|
||||
{
|
||||
if (_minHeight == null)
|
||||
return -500.0f;
|
||||
|
||||
uint[] indices =
|
||||
{
|
||||
3, 0, 4,
|
||||
0, 1, 4,
|
||||
1, 2, 4,
|
||||
2, 5, 4,
|
||||
5, 8, 4,
|
||||
8, 7, 4,
|
||||
7, 6, 4,
|
||||
6, 3, 4
|
||||
};
|
||||
|
||||
float[] boundGridCoords =
|
||||
{
|
||||
0.0f, 0.0f,
|
||||
0.0f, -266.66666f,
|
||||
0.0f, -533.33331f,
|
||||
-266.66666f, 0.0f,
|
||||
-266.66666f, -266.66666f,
|
||||
-266.66666f, -533.33331f,
|
||||
-533.33331f, 0.0f,
|
||||
-533.33331f, -266.66666f,
|
||||
-533.33331f, -533.33331f
|
||||
};
|
||||
|
||||
Cell cell = new Cell(x, y);
|
||||
float gx = x - (cell.GetGridX() - MapConst.CenterGridId + 1) *MapConst.SizeofGrids;
|
||||
float gy = y - (cell.GetGridY() - MapConst.CenterGridId + 1) *MapConst.SizeofGrids;
|
||||
|
||||
uint quarterIndex = 0;
|
||||
if (cell.GetCellY() < MapConst.MaxCells / 2)
|
||||
{
|
||||
if (cell.GetCellX() < MapConst.MaxCells / 2)
|
||||
{
|
||||
quarterIndex = 4 + (gy > gx ? 1u : 0u);
|
||||
}
|
||||
else
|
||||
quarterIndex = (2 + ((-MapConst.SizeofGrids - gx) > gy ? 1u : 0));
|
||||
}
|
||||
else if (cell.GetCellX() < MapConst.MaxCells / 2)
|
||||
{
|
||||
quarterIndex = 6 + ((-MapConst.SizeofGrids - gx) <= gy ? 1u : 0);
|
||||
}
|
||||
else
|
||||
quarterIndex = gx > gy ? 1u : 0;
|
||||
|
||||
quarterIndex *= 3;
|
||||
|
||||
return new Plane(
|
||||
new Vector3(boundGridCoords[indices[quarterIndex + 0] * 2 + 0], boundGridCoords[indices[quarterIndex + 0] * 2 + 1], _minHeight[indices[quarterIndex + 0]]),
|
||||
new Vector3(boundGridCoords[indices[quarterIndex + 1] * 2 + 0], boundGridCoords[indices[quarterIndex + 1] * 2 + 1], _minHeight[indices[quarterIndex + 1]]),
|
||||
new Vector3(boundGridCoords[indices[quarterIndex + 2] * 2 + 0], boundGridCoords[indices[quarterIndex + 2] * 2 + 1], _minHeight[indices[quarterIndex + 2]])
|
||||
).GetDistanceToPlane(new Vector3(gx, gy, 0.0f));
|
||||
}
|
||||
|
||||
public float getLiquidLevel(float x, float y)
|
||||
{
|
||||
if (_liquidMap == null)
|
||||
return _liquidLevel;
|
||||
|
||||
x = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
|
||||
y = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
|
||||
|
||||
int cx_int = ((int)x & (MapConst.MapResolution - 1)) - _liquidOffY;
|
||||
int cy_int = ((int)y & (MapConst.MapResolution - 1)) - _liquidOffX;
|
||||
|
||||
if (cx_int < 0 || cx_int >= _liquidHeight)
|
||||
return MapConst.InvalidHeight;
|
||||
if (cy_int < 0 || cy_int >= _liquidWidth)
|
||||
return MapConst.InvalidHeight;
|
||||
|
||||
return _liquidMap[cx_int * _liquidWidth + cy_int];
|
||||
}
|
||||
|
||||
// Why does this return LIQUID data?
|
||||
public byte getTerrainType(float x, float y)
|
||||
{
|
||||
if (_liquidFlags == null)
|
||||
return 0;
|
||||
|
||||
x = 16 * (32 - x / MapConst.SizeofGrids);
|
||||
y = 16 * (32 - y / MapConst.SizeofGrids);
|
||||
int lx = (int)x & 15;
|
||||
int ly = (int)y & 15;
|
||||
return _liquidFlags[lx * 16 + ly];
|
||||
}
|
||||
|
||||
// Get water state on map
|
||||
public ZLiquidStatus getLiquidStatus(float x, float y, float z, byte ReqLiquidType, LiquidData data)
|
||||
{
|
||||
// Check water type (if no water return)
|
||||
if (_liquidType == 0 && _liquidFlags == null)
|
||||
return ZLiquidStatus.NoWater;
|
||||
|
||||
// Get cell
|
||||
float cx = MapConst.MapResolution * (32 - x / MapConst.SizeofGrids);
|
||||
float cy = MapConst.MapResolution * (32 - y / MapConst.SizeofGrids);
|
||||
|
||||
int x_int = (int)cx & (MapConst.MapResolution - 1);
|
||||
int y_int = (int)cy & (MapConst.MapResolution - 1);
|
||||
|
||||
// Check water type in cell
|
||||
int idx = (x_int >> 3) * 16 + (y_int >> 3);
|
||||
byte type = _liquidFlags != null ? _liquidFlags[idx] : (byte)_liquidType;
|
||||
uint entry = 0;
|
||||
if (_liquidEntry != null)
|
||||
{
|
||||
var liquidEntry = CliDB.LiquidTypeStorage.LookupByKey(_liquidEntry[idx]);
|
||||
if (liquidEntry != null)
|
||||
{
|
||||
entry = liquidEntry.Id;
|
||||
type &= MapConst.MapLiquidTypeDarkWater;
|
||||
uint liqTypeIdx = liquidEntry.LiquidType;
|
||||
if (entry < 21)
|
||||
{
|
||||
var area = CliDB.AreaTableStorage.LookupByKey(getArea(x, y));
|
||||
if (area != null)
|
||||
{
|
||||
uint overrideLiquid = area.LiquidTypeID[liquidEntry.LiquidType];
|
||||
if (overrideLiquid == 0 && area.ParentAreaID == 0)
|
||||
{
|
||||
area = CliDB.AreaTableStorage.LookupByKey(area.ParentAreaID);
|
||||
if (area != null)
|
||||
overrideLiquid = area.LiquidTypeID[liquidEntry.LiquidType];
|
||||
}
|
||||
var liq = CliDB.LiquidTypeStorage.LookupByKey(overrideLiquid);
|
||||
if (liq != null)
|
||||
{
|
||||
entry = overrideLiquid;
|
||||
liqTypeIdx = liq.LiquidType;
|
||||
}
|
||||
}
|
||||
}
|
||||
type |= (byte)(1 << (int)liqTypeIdx);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == 0)
|
||||
return ZLiquidStatus.NoWater;
|
||||
|
||||
// Check req liquid type mask
|
||||
if (ReqLiquidType != 0 && !Convert.ToBoolean(ReqLiquidType & type))
|
||||
return ZLiquidStatus.NoWater;
|
||||
|
||||
// Check water level:
|
||||
// Check water height map
|
||||
int lx_int = x_int - _liquidOffY;
|
||||
int ly_int = y_int - _liquidOffX;
|
||||
if (lx_int < 0 || lx_int >= _liquidHeight)
|
||||
return ZLiquidStatus.NoWater;
|
||||
if (ly_int < 0 || ly_int >= _liquidWidth)
|
||||
return ZLiquidStatus.NoWater;
|
||||
|
||||
// Get water level
|
||||
float liquid_level = _liquidMap != null ? _liquidMap[lx_int * _liquidWidth + ly_int] : _liquidLevel;
|
||||
// Get ground level (sub 0.2 for fix some errors)
|
||||
float ground_level = getHeight(x, y);
|
||||
|
||||
// Check water level and ground level
|
||||
if (liquid_level < ground_level || z < ground_level - 2)
|
||||
return ZLiquidStatus.NoWater;
|
||||
|
||||
// All ok in water . store data
|
||||
if (data != null)
|
||||
{
|
||||
data.entry = entry;
|
||||
data.type_flags = type;
|
||||
data.level = liquid_level;
|
||||
data.depth_level = ground_level;
|
||||
}
|
||||
|
||||
// For speed check as int values
|
||||
float delta = liquid_level - z;
|
||||
|
||||
if (delta > 2.0f) // Under water
|
||||
return ZLiquidStatus.UnderWater;
|
||||
if (delta > 0.0f) // In water
|
||||
return ZLiquidStatus.InWater;
|
||||
if (delta > -0.1f) // Walk on water
|
||||
return ZLiquidStatus.WaterWalk;
|
||||
// Above water
|
||||
return ZLiquidStatus.AboveWater;
|
||||
}
|
||||
|
||||
public float getHeight(float x, float y) { return _gridGetHeight(x, y); }
|
||||
|
||||
#region Fields
|
||||
delegate float GetHeight(float x, float y);
|
||||
|
||||
GetHeight _gridGetHeight;
|
||||
uint _flags;
|
||||
|
||||
public float[] m_V9;
|
||||
public ushort[] m_uint16_V9;
|
||||
public byte[] m_ubyte_V9;
|
||||
|
||||
public float[] m_V8;
|
||||
public ushort[] m_uint16_V8;
|
||||
public byte[] m_ubyte_V8;
|
||||
short[] _maxHeight;
|
||||
short[] _minHeight;
|
||||
float _gridHeight;
|
||||
float _gridIntHeightMultiplier;
|
||||
|
||||
//Area data
|
||||
public ushort[] _areaMap;
|
||||
|
||||
//Liquid Map
|
||||
float _liquidLevel;
|
||||
ushort[] _liquidEntry;
|
||||
byte[] _liquidFlags;
|
||||
float[] _liquidMap;
|
||||
ushort _gridArea;
|
||||
ushort _liquidType;
|
||||
byte _liquidOffX;
|
||||
byte _liquidOffY;
|
||||
byte _liquidWidth;
|
||||
byte _liquidHeight;
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct mapFileHeader
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public char[] mapMagic;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public char[] versionMagic;
|
||||
public uint buildMagic;
|
||||
public uint areaMapOffset;
|
||||
public uint areaMapSize;
|
||||
public uint heightMapOffset;
|
||||
public uint heightMapSize;
|
||||
public uint liquidMapOffset;
|
||||
public uint liquidMapSize;
|
||||
public uint holesOffset;
|
||||
public uint holesSize;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct map_AreaHeader
|
||||
{
|
||||
public uint fourcc;
|
||||
public AreaHeaderFlags flags;
|
||||
public ushort gridArea;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct map_HeightHeader
|
||||
{
|
||||
public uint fourcc;
|
||||
public HeightHeaderFlags flags;
|
||||
public float gridHeight;
|
||||
public float gridMaxHeight;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct map_LiquidHeader
|
||||
{
|
||||
public uint fourcc;
|
||||
public LiquidHeaderFlags flags;
|
||||
public ushort liquidType;
|
||||
public byte offsetX;
|
||||
public byte offsetY;
|
||||
public byte width;
|
||||
public byte height;
|
||||
public float liquidLevel;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class LiquidData
|
||||
{
|
||||
public uint type_flags;
|
||||
public uint entry;
|
||||
public float level;
|
||||
public float depth_level;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AreaHeaderFlags : ushort
|
||||
{
|
||||
NoArea = 0x0001
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HeightHeaderFlags
|
||||
{
|
||||
NoHeight = 0x0001,
|
||||
HeightAsInt16 = 0x0002,
|
||||
HeightAsInt8 = 0x0004,
|
||||
HeightHasFlightBounds = 0x0008
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum LiquidHeaderFlags : ushort
|
||||
{
|
||||
LiquidNoType = 0x0001,
|
||||
LiquidNoHeight = 0x0002
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,779 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Game.DataStorage;
|
||||
using Game.Entities;
|
||||
using Game.Groups;
|
||||
using Game.Scenarios;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class InstanceSaveManager : Singleton<InstanceSaveManager>
|
||||
{
|
||||
InstanceSaveManager() { }
|
||||
|
||||
public InstanceSave AddInstanceSave(uint mapId, uint instanceId, Difficulty difficulty, long resetTime, uint entranceId, bool canReset, bool load = false)
|
||||
{
|
||||
InstanceSave old_save = GetInstanceSave(instanceId);
|
||||
if (old_save != null)
|
||||
return old_save;
|
||||
|
||||
MapRecord entry = CliDB.MapStorage.LookupByKey(mapId);
|
||||
if (entry == null)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: wrong mapid = {0}, instanceid = {1}!", mapId, instanceId);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (instanceId == 0)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, wrong instanceid = {1}!", mapId, instanceId);
|
||||
return null;
|
||||
}
|
||||
|
||||
DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty);
|
||||
if (difficultyEntry == null || difficultyEntry.InstanceType != entry.InstanceType)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}, wrong dificalty {2}!", mapId, instanceId, difficulty);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entranceId != 0 && !CliDB.WorldSafeLocsStorage.ContainsKey(entranceId))
|
||||
{
|
||||
Log.outWarn(LogFilter.Misc, "InstanceSaveManager.AddInstanceSave: invalid entranceId = {0} defined for instance save with mapid = {1}, instanceid = {2}!", entranceId, mapId, instanceId);
|
||||
entranceId = 0;
|
||||
}
|
||||
|
||||
if (resetTime == 0)
|
||||
{
|
||||
// initialize reset time
|
||||
// for normal instances if no creatures are killed the instance will reset in two hours
|
||||
if (entry.InstanceType == MapTypes.Raid || difficulty > Difficulty.Normal)
|
||||
resetTime = GetResetTimeFor(mapId, difficulty);
|
||||
else
|
||||
{
|
||||
resetTime = Time.UnixTime + 2 * Time.Hour;
|
||||
// normally this will be removed soon after in InstanceMap.Add, prevent error
|
||||
ScheduleReset(true, resetTime, new InstResetEvent(0, mapId, difficulty, instanceId));
|
||||
}
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Maps, "InstanceSaveManager.AddInstanceSave: mapid = {0}, instanceid = {1}", mapId, instanceId);
|
||||
|
||||
InstanceSave save = new InstanceSave(mapId, instanceId, difficulty, entranceId, resetTime, canReset);
|
||||
if (!load)
|
||||
save.SaveToDB();
|
||||
|
||||
m_instanceSaveById[instanceId] = save;
|
||||
return save;
|
||||
}
|
||||
|
||||
public InstanceSave GetInstanceSave(uint InstanceId)
|
||||
{
|
||||
return m_instanceSaveById.LookupByKey(InstanceId);
|
||||
}
|
||||
|
||||
public void DeleteInstanceFromDB(uint instanceid)
|
||||
{
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_INSTANCE_BY_INSTANCE);
|
||||
stmt.AddValue(0, instanceid);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_CHAR_INSTANCE_BY_INSTANCE);
|
||||
stmt.AddValue(0, instanceid);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_INSTANCE);
|
||||
stmt.AddValue(0, instanceid);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_SCENARIO_INSTANCE_CRITERIA_FOR_INSTANCE);
|
||||
stmt.AddValue(0, instanceid);
|
||||
trans.Append(stmt);
|
||||
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
// Respawn times should be deleted only when the map gets unloaded
|
||||
}
|
||||
|
||||
public void RemoveInstanceSave(uint InstanceId)
|
||||
{
|
||||
var instanceSave = m_instanceSaveById.LookupByKey(InstanceId);
|
||||
if (instanceSave != null)
|
||||
{
|
||||
// save the resettime for normal instances only when they get unloaded
|
||||
long resettime = instanceSave.GetResetTimeForDB();
|
||||
if (resettime != 0)
|
||||
{
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_INSTANCE_RESETTIME);
|
||||
|
||||
stmt.AddValue(0, resettime);
|
||||
stmt.AddValue(1, InstanceId);
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
instanceSave.SetToDelete(true);
|
||||
m_instanceSaveById.Remove(InstanceId);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadInstances()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
// Delete expired instances (Instance related spawns are removed in the following cleanup queries)
|
||||
DB.Characters.Execute("DELETE i FROM instance i LEFT JOIN instance_reset ir ON mapid = map AND i.difficulty = ir.difficulty " +
|
||||
"WHERE (i.resettime > 0 AND i.resettime < UNIX_TIMESTAMP()) OR (ir.resettime IS NOT NULL AND ir.resettime < UNIX_TIMESTAMP())");
|
||||
|
||||
// Delete invalid character_instance and group_instance references
|
||||
DB.Characters.Execute("DELETE ci.* FROM character_instance AS ci LEFT JOIN characters AS c ON ci.guid = c.guid WHERE c.guid IS NULL");
|
||||
DB.Characters.Execute("DELETE gi.* FROM group_instance AS gi LEFT JOIN groups AS g ON gi.guid = g.guid WHERE g.guid IS NULL");
|
||||
|
||||
// Delete invalid instance references
|
||||
DB.Characters.Execute("DELETE i.* FROM instance AS i LEFT JOIN character_instance AS ci ON i.id = ci.instance LEFT JOIN group_instance AS gi ON i.id = gi.instance WHERE ci.guid IS NULL AND gi.guid IS NULL");
|
||||
|
||||
// Delete invalid references to instance
|
||||
DB.Characters.Execute("DELETE FROM creature_respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
|
||||
DB.Characters.Execute("DELETE FROM gameobject_respawn WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
|
||||
DB.Characters.Execute("DELETE tmp.* FROM character_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL");
|
||||
DB.Characters.Execute("DELETE tmp.* FROM group_instance AS tmp LEFT JOIN instance ON tmp.instance = instance.id WHERE tmp.instance > 0 AND instance.id IS NULL");
|
||||
|
||||
// Clean invalid references to instance
|
||||
DB.Characters.Execute("UPDATE corpse SET instanceId = 0 WHERE instanceId > 0 AND instanceId NOT IN (SELECT id FROM instance)");
|
||||
DB.Characters.Execute("UPDATE characters AS tmp LEFT JOIN instance ON tmp.instance_id = instance.id SET tmp.instance_id = 0 WHERE tmp.instance_id > 0 AND instance.id IS NULL");
|
||||
|
||||
// Initialize instance id storage (Needs to be done after the trash has been clean out)
|
||||
Global.MapMgr.InitInstanceIds();
|
||||
|
||||
// Load reset times and clean expired instances
|
||||
LoadResetTimes();
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded instances in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
void LoadResetTimes()
|
||||
{
|
||||
long now = Time.UnixTime;
|
||||
long today = (now / Time.Day) * Time.Day;
|
||||
|
||||
// NOTE: Use DirectPExecute for tables that will be queried later
|
||||
|
||||
// get the current reset times for normal instances (these may need to be updated)
|
||||
// these are only kept in memory for InstanceSaves that are loaded later
|
||||
// resettime = 0 in the DB for raid/heroic instances so those are skipped
|
||||
Dictionary<uint, Tuple<uint, long>> instResetTime = new Dictionary<uint, Tuple<uint, long>>();
|
||||
|
||||
// index instance ids by map/difficulty pairs for fast reset warning send
|
||||
MultiMap<uint, uint> mapDiffResetInstances = new MultiMap<uint, uint>();
|
||||
|
||||
SQLResult result = DB.Characters.Query("SELECT id, map, difficulty, resettime FROM instance ORDER BY id ASC");
|
||||
if (!result.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
uint instanceId = result.Read<uint>(0);
|
||||
|
||||
// Instances are pulled in ascending order from db and nextInstanceId is initialized with 1,
|
||||
// so if the instance id is used, increment until we find the first unused one for a potential new instance
|
||||
if (Global.MapMgr.GetNextInstanceId() == instanceId)
|
||||
Global.MapMgr.SetNextInstanceId(instanceId + 1);
|
||||
|
||||
// Mark instance id as being used
|
||||
Global.MapMgr.RegisterInstanceId(instanceId);
|
||||
long resettime = result.Read<uint>(3);
|
||||
if (resettime != 0)
|
||||
{
|
||||
uint mapid = result.Read<ushort>(1);
|
||||
uint difficulty = result.Read<byte>(2);
|
||||
|
||||
instResetTime[instanceId] = Tuple.Create(MathFunctions.MakePair32(mapid, difficulty), resettime);
|
||||
mapDiffResetInstances.Add(MathFunctions.MakePair32(mapid, difficulty), instanceId);
|
||||
}
|
||||
}
|
||||
while (result.NextRow());
|
||||
|
||||
// update reset time for normal instances with the max creature respawn time + X hours
|
||||
SQLResult result2 = DB.Characters.Query(DB.Characters.GetPreparedStatement(CharStatements.SEL_MAX_CREATURE_RESPAWNS));
|
||||
if (!result2.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
uint instance = result2.Read<uint>(1);
|
||||
long resettime = result2.Read<uint>(0) + 2 * Time.Hour;
|
||||
var pair = instResetTime.LookupByKey(instance);
|
||||
if (pair != null && pair.Item2 != resettime)
|
||||
{
|
||||
DB.Characters.Execute("UPDATE instance SET resettime = '{0}' WHERE id = '{1}'", resettime, instance);
|
||||
instResetTime[instance] = Tuple.Create(pair.Item1, resettime);
|
||||
}
|
||||
}
|
||||
while (result2.NextRow());
|
||||
}
|
||||
|
||||
// schedule the reset times
|
||||
foreach (var pair in instResetTime)
|
||||
if (pair.Value.Item2 > now)
|
||||
ScheduleReset(true, pair.Value.Item2, new InstResetEvent(0, MathFunctions.Pair32_LoPart(pair.Value.Item1), (Difficulty)MathFunctions.Pair32_HiPart(pair.Value.Item1), pair.Key));
|
||||
}
|
||||
|
||||
// load the global respawn times for raid/heroic instances
|
||||
uint diff = (uint)(WorldConfig.GetIntValue(WorldCfg.InstanceResetTimeHour) * Time.Hour);
|
||||
result = DB.Characters.Query("SELECT mapid, difficulty, resettime FROM instance_reset");
|
||||
if (!result.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
uint mapid = result.Read<ushort>(0);
|
||||
Difficulty difficulty = (Difficulty)result.Read<byte>(1);
|
||||
ulong oldresettime = result.Read<uint>(2);
|
||||
|
||||
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty);
|
||||
if (mapDiff == null)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.LoadResetTimes: invalid mapid({0})/difficulty({1}) pair in instance_reset!", mapid, difficulty);
|
||||
DB.Characters.Execute("DELETE FROM instance_reset WHERE mapid = '{0}' AND difficulty = '{1}'", mapid, difficulty);
|
||||
continue;
|
||||
}
|
||||
|
||||
// update the reset time if the hour in the configs changes
|
||||
ulong newresettime = (oldresettime / Time.Day) * Time.Day + diff;
|
||||
if (oldresettime != newresettime)
|
||||
DB.Characters.Execute("UPDATE instance_reset SET resettime = '{0}' WHERE mapid = '{1}' AND difficulty = '{2}'", newresettime, mapid, difficulty);
|
||||
|
||||
InitializeResetTimeFor(mapid, difficulty, (long)newresettime);
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
// calculate new global reset times for expired instances and those that have never been reset yet
|
||||
// add the global reset times to the priority queue
|
||||
foreach (var mapDifficultyPair in Global.DB2Mgr.GetMapDifficulties())
|
||||
{
|
||||
uint mapid = mapDifficultyPair.Key;
|
||||
|
||||
foreach (var difficultyPair in mapDifficultyPair.Value)
|
||||
{
|
||||
Difficulty difficulty = (Difficulty)difficultyPair.Key;
|
||||
MapDifficultyRecord mapDiff = difficultyPair.Value;
|
||||
if (mapDiff.GetRaidDuration() == 0)
|
||||
continue;
|
||||
|
||||
// the reset_delay must be at least one day
|
||||
uint period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day);
|
||||
if (period < Time.Day)
|
||||
period = Time.Day;
|
||||
|
||||
long t = GetResetTimeFor(mapid, difficulty);
|
||||
if (t == 0)
|
||||
{
|
||||
// initialize the reset time
|
||||
t = today + period + diff;
|
||||
DB.Characters.Execute("INSERT INTO instance_reset VALUES ('{0}', '{1}', '{2}')", mapid, (uint)difficulty, (uint)t);
|
||||
}
|
||||
|
||||
if (t < now)
|
||||
{
|
||||
// assume that expired instances have already been cleaned
|
||||
// calculate the next reset time
|
||||
t = (t / Time.Day) * Time.Day;
|
||||
t += ((today - t) / period + 1) * period + diff;
|
||||
DB.Characters.Execute("UPDATE instance_reset SET resettime = '{0}' WHERE mapid = '{1}' AND difficulty= '{2}'", t, mapid, (uint)difficulty);
|
||||
}
|
||||
|
||||
InitializeResetTimeFor(mapid, difficulty, t);
|
||||
|
||||
// schedule the global reset/warning
|
||||
byte type;
|
||||
for (type = 1; type < 4; ++type)
|
||||
if (t - ResetTimeDelay[type - 1] > now)
|
||||
break;
|
||||
|
||||
ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, 0));
|
||||
|
||||
var range = mapDiffResetInstances.LookupByKey(MathFunctions.MakePair32(mapid, (uint)difficulty));
|
||||
foreach (var id in range)
|
||||
ScheduleReset(true, t - ResetTimeDelay[type - 1], new InstResetEvent(type, mapid, difficulty, id));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long GetSubsequentResetTime(uint mapid, Difficulty difficulty, long resetTime)
|
||||
{
|
||||
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetMapDifficultyData(mapid, difficulty);
|
||||
if (mapDiff == null || mapDiff.GetRaidDuration() == 0)
|
||||
{
|
||||
Log.outError(LogFilter.Misc, "InstanceSaveManager.GetSubsequentResetTime: not valid difficulty or no reset delay for map {0}", mapid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
long diff = WorldConfig.GetIntValue(WorldCfg.InstanceResetTimeHour) * Time.Hour;
|
||||
long period = (uint)(((mapDiff.GetRaidDuration() * WorldConfig.GetFloatValue(WorldCfg.RateInstanceResetTime)) / Time.Day) * Time.Day);
|
||||
if (period < Time.Day)
|
||||
period = Time.Day;
|
||||
|
||||
return ((resetTime + Time.Minute) / Time.Day * Time.Day) + period + diff;
|
||||
}
|
||||
|
||||
public void ScheduleReset(bool add, long time, InstResetEvent Event)
|
||||
{
|
||||
if (!add)
|
||||
{
|
||||
// find the event in the queue and remove it
|
||||
var range = m_resetTimeQueue.LookupByKey(time);
|
||||
foreach (var instResetEvent in range)
|
||||
{
|
||||
if (instResetEvent == Event)
|
||||
{
|
||||
m_resetTimeQueue.Remove(time, instResetEvent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// in case the reset time changed (should happen very rarely), we search the whole queue
|
||||
foreach (var pair in m_resetTimeQueue)
|
||||
{
|
||||
if (pair.Value == Event)
|
||||
{
|
||||
m_resetTimeQueue.Remove(pair);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.outError(LogFilter.Server, "InstanceSaveManager.ScheduleReset: cannot cancel the reset, the event({0}, {1}, {2}) was not found!", Event.type, Event.mapid, Event.instanceId);
|
||||
}
|
||||
else
|
||||
m_resetTimeQueue.Add(time, Event);
|
||||
}
|
||||
|
||||
public void ForceGlobalReset(uint mapId, Difficulty difficulty)
|
||||
{
|
||||
if (Global.DB2Mgr.GetDownscaledMapDifficultyData(mapId, ref difficulty) == null)
|
||||
return;
|
||||
// remove currently scheduled reset times
|
||||
ScheduleReset(false, 0, new InstResetEvent(1, mapId, difficulty, 0));
|
||||
ScheduleReset(false, 0, new InstResetEvent(4, mapId, difficulty, 0));
|
||||
// force global reset on the instance
|
||||
_ResetOrWarnAll(mapId, difficulty, false, Time.UnixTime);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
long now = Time.UnixTime;
|
||||
long t;
|
||||
|
||||
while (!m_resetTimeQueue.Empty())
|
||||
{
|
||||
t = m_resetTimeQueue.First().Key;
|
||||
if (t >= now)
|
||||
break;
|
||||
|
||||
InstResetEvent Event = m_resetTimeQueue.First().Value;
|
||||
if (Event.type == 0)
|
||||
{
|
||||
// for individual normal instances, max creature respawn + X hours
|
||||
_ResetInstance(Event.mapid, Event.instanceId);
|
||||
m_resetTimeQueue.Remove(m_resetTimeQueue.First());
|
||||
}
|
||||
else
|
||||
{
|
||||
// global reset/warning for a certain map
|
||||
long resetTime = GetResetTimeFor(Event.mapid, Event.difficulty);
|
||||
_ResetOrWarnAll(Event.mapid, Event.difficulty, Event.type != 4, resetTime);
|
||||
if (Event.type != 4)
|
||||
{
|
||||
// schedule the next warning/reset
|
||||
++Event.type;
|
||||
ScheduleReset(true, resetTime - ResetTimeDelay[Event.type - 1], Event);
|
||||
}
|
||||
m_resetTimeQueue.Remove(m_resetTimeQueue.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _ResetSave(KeyValuePair<uint, InstanceSave> pair)
|
||||
{
|
||||
// unbind all players bound to the instance
|
||||
// do not allow UnbindInstance to automatically unload the InstanceSaves
|
||||
lock_instLists = true;
|
||||
|
||||
bool shouldDelete = true;
|
||||
var pList = pair.Value.m_playerList;
|
||||
List<Player> temp = new List<Player>(); // list of expired binds that should be unbound
|
||||
foreach (var player in pList)
|
||||
{
|
||||
InstanceBind bind = player.GetBoundInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID());
|
||||
if (bind != null)
|
||||
{
|
||||
Contract.Assert(bind.save == pair.Value);
|
||||
if (bind.perm && bind.extendState != 0) // permanent and not already expired
|
||||
{
|
||||
// actual promotion in DB already happened in caller
|
||||
bind.extendState = bind.extendState == BindExtensionState.Extended ? BindExtensionState.Normal : BindExtensionState.Expired;
|
||||
shouldDelete = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
temp.Add(player);
|
||||
}
|
||||
|
||||
var gList = pair.Value.m_groupList;
|
||||
while (!gList.Empty())
|
||||
{
|
||||
Group group = gList.First();
|
||||
group.UnbindInstance(pair.Value.GetMapId(), pair.Value.GetDifficultyID(), true);
|
||||
}
|
||||
|
||||
if (shouldDelete)
|
||||
m_instanceSaveById.Remove(pair.Key);
|
||||
|
||||
lock_instLists = false;
|
||||
}
|
||||
|
||||
void _ResetInstance(uint mapid, uint instanceId)
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "InstanceSaveMgr._ResetInstance {0}, {1}", mapid, instanceId);
|
||||
Map map = Global.MapMgr.CreateBaseMap(mapid);
|
||||
if (!map.Instanceable())
|
||||
return;
|
||||
|
||||
var pair = m_instanceSaveById.Find(instanceId);
|
||||
if (pair.Value != null)
|
||||
_ResetSave(pair);
|
||||
|
||||
DeleteInstanceFromDB(instanceId); // even if save not loaded
|
||||
|
||||
Map iMap = ((MapInstanced)map).FindInstanceMap(instanceId);
|
||||
|
||||
if (iMap != null && iMap.IsDungeon())
|
||||
((InstanceMap)iMap).Reset(InstanceResetMethod.RespawnDelay);
|
||||
|
||||
if (iMap != null)
|
||||
{
|
||||
iMap.DeleteRespawnTimes();
|
||||
iMap.DeleteCorpseData();
|
||||
}
|
||||
else
|
||||
Map.DeleteRespawnTimesInDB(mapid, instanceId);
|
||||
|
||||
// Free up the instance id and allow it to be reused
|
||||
Global.MapMgr.FreeInstanceId(instanceId);
|
||||
}
|
||||
|
||||
void _ResetOrWarnAll(uint mapid, Difficulty difficulty, bool warn, long resetTime)
|
||||
{
|
||||
// global reset for all instances of the given map
|
||||
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapid);
|
||||
if (!mapEntry.Instanceable())
|
||||
return;
|
||||
|
||||
Log.outDebug(LogFilter.Misc, "InstanceSaveManager.ResetOrWarnAll: Processing map {0} ({1}) on difficulty {2} (warn? {3})", mapEntry.MapName[Global.WorldMgr.GetDefaultDbcLocale()], mapid, difficulty, warn);
|
||||
long now = Time.UnixTime;
|
||||
|
||||
if (!warn)
|
||||
{
|
||||
// calculate the next reset time
|
||||
long next_reset = GetSubsequentResetTime(mapid, difficulty, resetTime);
|
||||
if (next_reset == 0)
|
||||
return;
|
||||
|
||||
// delete them from the DB, even if not loaded
|
||||
SQLTransaction trans = new SQLTransaction();
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF);
|
||||
stmt.AddValue(0, mapid);
|
||||
stmt.AddValue(1, (byte)difficulty);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_GROUP_INSTANCE_BY_MAP_DIFF);
|
||||
stmt.AddValue(0, mapid);
|
||||
stmt.AddValue(1, (byte)difficulty);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_EXPIRED_INSTANCE_BY_MAP_DIFF);
|
||||
stmt.AddValue(0, mapid);
|
||||
stmt.AddValue(1, (byte)difficulty);
|
||||
trans.Append(stmt);
|
||||
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF);
|
||||
stmt.AddValue(0, mapid);
|
||||
stmt.AddValue(1, (byte)difficulty);
|
||||
trans.Append(stmt);
|
||||
|
||||
DB.Characters.CommitTransaction(trans);
|
||||
|
||||
// promote loaded binds to instances of the given map
|
||||
foreach (var pair in m_instanceSaveById.ToList())
|
||||
{
|
||||
if (pair.Value.GetMapId() == mapid && pair.Value.GetDifficultyID() == difficulty)
|
||||
_ResetSave(pair);
|
||||
}
|
||||
|
||||
SetResetTimeFor(mapid, difficulty, next_reset);
|
||||
ScheduleReset(true, next_reset - 3600, new InstResetEvent(1, mapid, difficulty, 0));
|
||||
|
||||
// Update it in the DB
|
||||
stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_GLOBAL_INSTANCE_RESETTIME);
|
||||
stmt.AddValue(0, next_reset);
|
||||
stmt.AddValue(1, mapid);
|
||||
stmt.AddValue(2, difficulty);
|
||||
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
// note: this isn't fast but it's meant to be executed very rarely
|
||||
Map map = Global.MapMgr.CreateBaseMap(mapid); // _not_ include difficulty
|
||||
var instMaps = ((MapInstanced)map).GetInstancedMaps();
|
||||
uint timeLeft;
|
||||
|
||||
foreach (var pair in instMaps)
|
||||
{
|
||||
Map map2 = pair.Value;
|
||||
if (!map2.IsDungeon())
|
||||
continue;
|
||||
|
||||
if (warn)
|
||||
{
|
||||
if (now >= resetTime)
|
||||
timeLeft = 0;
|
||||
else
|
||||
timeLeft = (uint)(resetTime - now);
|
||||
|
||||
((InstanceMap)map2).SendResetWarnings(timeLeft);
|
||||
}
|
||||
else
|
||||
((InstanceMap)map2).Reset(InstanceResetMethod.Global);
|
||||
}
|
||||
|
||||
/// @todo delete creature/gameobject respawn times even if the maps are not loaded
|
||||
}
|
||||
|
||||
public uint GetNumBoundPlayersTotal()
|
||||
{
|
||||
uint ret = 0;
|
||||
foreach (var pair in m_instanceSaveById)
|
||||
ret += pair.Value.GetPlayerCount();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public uint GetNumBoundGroupsTotal()
|
||||
{
|
||||
uint ret = 0;
|
||||
foreach (var pair in m_instanceSaveById)
|
||||
ret += pair.Value.GetGroupCount();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
public long GetResetTimeFor(uint mapid, Difficulty d)
|
||||
{
|
||||
return m_resetTimeByMapDifficulty.LookupByKey(MathFunctions.MakePair64(mapid, (uint)d));
|
||||
}
|
||||
|
||||
// Use this on startup when initializing reset times
|
||||
void InitializeResetTimeFor(uint mapid, Difficulty d, long t)
|
||||
{
|
||||
m_resetTimeByMapDifficulty[MathFunctions.MakePair64(mapid, (uint)d)] = t;
|
||||
}
|
||||
// Use this only when updating existing reset times
|
||||
void SetResetTimeFor(uint mapid, Difficulty d, long t)
|
||||
{
|
||||
var key = MathFunctions.MakePair64(mapid, (uint)d);
|
||||
Contract.Assert(m_resetTimeByMapDifficulty.ContainsKey(key));
|
||||
m_resetTimeByMapDifficulty[key] = t;
|
||||
}
|
||||
|
||||
public Dictionary<ulong, long> GetResetTimeMap()
|
||||
{
|
||||
return m_resetTimeByMapDifficulty;
|
||||
}
|
||||
|
||||
public int GetNumInstanceSaves() { return m_instanceSaveById.Count; }
|
||||
|
||||
public class InstResetEvent
|
||||
{
|
||||
public InstResetEvent(byte t = 0, uint _mapid = 0, Difficulty d = Difficulty.Normal, uint _instanceid = 0)
|
||||
{
|
||||
type = t;
|
||||
difficulty = d;
|
||||
mapid = _mapid;
|
||||
instanceId = _instanceid;
|
||||
}
|
||||
|
||||
public byte type;
|
||||
public Difficulty difficulty;
|
||||
public uint mapid;
|
||||
public uint instanceId;
|
||||
}
|
||||
|
||||
static ushort[] ResetTimeDelay = { 3600, 900, 300, 60 };
|
||||
|
||||
// used during global instance resets
|
||||
public bool lock_instLists;
|
||||
// fast lookup by instance id
|
||||
Dictionary<uint, InstanceSave> m_instanceSaveById = new Dictionary<uint, InstanceSave>();
|
||||
// fast lookup for reset times (always use existed functions for access/set)
|
||||
Dictionary<ulong, long> m_resetTimeByMapDifficulty = new Dictionary<ulong, long>();
|
||||
MultiMap<long, InstResetEvent> m_resetTimeQueue = new MultiMap<long, InstResetEvent>();
|
||||
}
|
||||
|
||||
public class InstanceSave
|
||||
{
|
||||
public InstanceSave(uint MapId, uint InstanceId, Difficulty difficulty, uint entranceId, long resetTime, bool canReset)
|
||||
{
|
||||
m_resetTime = resetTime;
|
||||
m_instanceid = InstanceId;
|
||||
m_mapid = MapId;
|
||||
m_difficulty = difficulty;
|
||||
m_entranceId = entranceId;
|
||||
m_canReset = canReset;
|
||||
m_toDelete = false;
|
||||
}
|
||||
|
||||
public void SaveToDB()
|
||||
{
|
||||
// save instance data too
|
||||
string data = "";
|
||||
uint completedEncounters = 0;
|
||||
|
||||
Map map = Global.MapMgr.FindMap(GetMapId(), m_instanceid);
|
||||
if (map != null)
|
||||
{
|
||||
Contract.Assert(map.IsDungeon());
|
||||
InstanceScript instanceScript = ((InstanceMap)map).GetInstanceScript();
|
||||
if (instanceScript != null)
|
||||
{
|
||||
data = instanceScript.GetSaveData();
|
||||
completedEncounters = instanceScript.GetCompletedEncounterMask();
|
||||
m_entranceId = instanceScript.GetEntranceLocation();
|
||||
}
|
||||
|
||||
InstanceScenario scenario = map.ToInstanceMap().GetInstanceScenario();
|
||||
if (scenario != null)
|
||||
scenario.SaveToDB();
|
||||
}
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_INSTANCE_SAVE);
|
||||
stmt.AddValue(0, m_instanceid);
|
||||
stmt.AddValue(1, GetMapId());
|
||||
stmt.AddValue(2, GetResetTimeForDB());
|
||||
stmt.AddValue(3, (uint)GetDifficultyID());
|
||||
stmt.AddValue(4, completedEncounters);
|
||||
stmt.AddValue(5, data);
|
||||
stmt.AddValue(6, m_entranceId);
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
public long GetResetTimeForDB()
|
||||
{
|
||||
// only save the reset time for normal instances
|
||||
MapRecord entry = CliDB.MapStorage.LookupByKey(GetMapId());
|
||||
if (entry == null || entry.InstanceType == MapTypes.Raid || GetDifficultyID() == Difficulty.Heroic)
|
||||
return 0;
|
||||
else
|
||||
return GetResetTime();
|
||||
}
|
||||
|
||||
InstanceTemplate GetTemplate()
|
||||
{
|
||||
return Global.ObjectMgr.GetInstanceTemplate(m_mapid);
|
||||
}
|
||||
|
||||
MapRecord GetMapEntry()
|
||||
{
|
||||
return CliDB.MapStorage.LookupByKey(m_mapid);
|
||||
}
|
||||
|
||||
public void DeleteFromDB()
|
||||
{
|
||||
Global.InstanceSaveMgr.DeleteInstanceFromDB(GetInstanceId());
|
||||
}
|
||||
|
||||
bool UnloadIfEmpty()
|
||||
{
|
||||
if (m_playerList.Empty() && m_groupList.Empty())
|
||||
{
|
||||
if (!Global.InstanceSaveMgr.lock_instLists)
|
||||
Global.InstanceSaveMgr.RemoveInstanceSave(GetInstanceId());
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
public uint GetPlayerCount() { return (uint)m_playerList.Count; }
|
||||
public uint GetGroupCount() { return (uint)m_groupList.Count; }
|
||||
|
||||
public uint GetInstanceId() { return m_instanceid; }
|
||||
public uint GetMapId() { return m_mapid; }
|
||||
|
||||
public long GetResetTime() { return m_resetTime; }
|
||||
public void SetResetTime(long resetTime) { m_resetTime = resetTime; }
|
||||
|
||||
public uint GetEntranceLocation() { return m_entranceId; }
|
||||
void SetEntranceLocation(uint entranceId) { m_entranceId = entranceId; }
|
||||
|
||||
public void AddPlayer(Player player)
|
||||
{
|
||||
m_playerList.Add(player);
|
||||
}
|
||||
public bool RemovePlayer(Player player)
|
||||
{
|
||||
m_playerList.Remove(player);
|
||||
|
||||
return UnloadIfEmpty();
|
||||
}
|
||||
|
||||
public void AddGroup(Group group) { m_groupList.Add(group); }
|
||||
public bool RemoveGroup(Group group)
|
||||
{
|
||||
m_groupList.Remove(group);
|
||||
|
||||
return UnloadIfEmpty();
|
||||
}
|
||||
|
||||
public bool CanReset() { return m_canReset; }
|
||||
public void SetCanReset(bool canReset) { m_canReset = canReset; }
|
||||
|
||||
public Difficulty GetDifficultyID() { return m_difficulty; }
|
||||
|
||||
public void SetToDelete(bool toDelete)
|
||||
{
|
||||
m_toDelete = toDelete;
|
||||
}
|
||||
|
||||
public List<Player> m_playerList = new List<Player>();
|
||||
public List<Group> m_groupList = new List<Group>();
|
||||
long m_resetTime;
|
||||
uint m_instanceid;
|
||||
uint m_mapid;
|
||||
Difficulty m_difficulty;
|
||||
uint m_entranceId;
|
||||
bool m_canReset;
|
||||
bool m_toDelete;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,946 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Framework.IO;
|
||||
using Game.Entities;
|
||||
using Game.Groups;
|
||||
using Game.Network.Packets;
|
||||
using Game.Scenarios;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Text;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class InstanceScript : ZoneScript
|
||||
{
|
||||
public InstanceScript(Map map)
|
||||
{
|
||||
instance = map;
|
||||
}
|
||||
|
||||
public void SaveToDB()
|
||||
{
|
||||
InstanceScenario scenario = instance.ToInstanceMap().GetInstanceScenario();
|
||||
if (scenario != null)
|
||||
scenario.SaveToDB();
|
||||
|
||||
string data = GetSaveData();
|
||||
if (string.IsNullOrEmpty(data))
|
||||
return;
|
||||
|
||||
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_INSTANCE_DATA);
|
||||
stmt.AddValue(0, GetCompletedEncounterMask());
|
||||
stmt.AddValue(1, data);
|
||||
stmt.AddValue(2, _entranceId);
|
||||
stmt.AddValue(3, instance.GetInstanceId());
|
||||
DB.Characters.Execute(stmt);
|
||||
}
|
||||
|
||||
public virtual bool IsEncounterInProgress()
|
||||
{
|
||||
foreach (var boss in bosses.Values)
|
||||
{
|
||||
if (boss.state == EncounterState.InProgress)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnCreatureCreate(Creature creature)
|
||||
{
|
||||
AddObject(creature, true);
|
||||
AddMinion(creature, true);
|
||||
}
|
||||
|
||||
public override void OnCreatureRemove(Creature creature)
|
||||
{
|
||||
AddObject(creature, false);
|
||||
AddMinion(creature, false);
|
||||
}
|
||||
|
||||
public override void OnGameObjectCreate(GameObject go)
|
||||
{
|
||||
AddObject(go, true);
|
||||
AddDoor(go, true);
|
||||
}
|
||||
|
||||
public override void OnGameObjectRemove(GameObject go)
|
||||
{
|
||||
AddObject(go, false);
|
||||
AddDoor(go, false);
|
||||
}
|
||||
|
||||
public ObjectGuid GetObjectGuid(uint type)
|
||||
{
|
||||
return _objectGuids.LookupByKey(type);
|
||||
}
|
||||
|
||||
public override ObjectGuid GetGuidData(uint type)
|
||||
{
|
||||
return GetObjectGuid(type);
|
||||
}
|
||||
|
||||
public void SetHeaders(string dataHeaders)
|
||||
{
|
||||
foreach (char header in dataHeaders)
|
||||
if (char.IsLetter(header))
|
||||
headers.Add(header);
|
||||
}
|
||||
|
||||
public void LoadBossBoundaries(BossBoundaryEntry[] data)
|
||||
{
|
||||
foreach (BossBoundaryEntry entry in data)
|
||||
{
|
||||
if (entry.BossId < bosses.Count)
|
||||
bosses[entry.BossId].boundary.Add(entry.Boundary);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadMinionData(params MinionData[] data)
|
||||
{
|
||||
foreach (var minion in data)
|
||||
{
|
||||
if (minion.entry == 0)
|
||||
continue;
|
||||
|
||||
if (minion.bossId < bosses.Count)
|
||||
minions.Add(minion.entry, new MinionInfo(bosses[minion.bossId]));
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript.LoadMinionData: {0} minions loaded.", minions.Count);
|
||||
}
|
||||
|
||||
public void LoadDoorData(params DoorData[] data)
|
||||
{
|
||||
foreach (var door in data)
|
||||
{
|
||||
if (door.entry == 0)
|
||||
continue;
|
||||
|
||||
if (door.bossId < bosses.Count)
|
||||
doors.Add(door.entry, new DoorInfo(bosses[door.bossId], door.type));
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript.LoadDoorData: {0} doors loaded.", doors.Count);
|
||||
}
|
||||
|
||||
public void LoadObjectData(ObjectData[] creatureData, ObjectData[] gameObjectData)
|
||||
{
|
||||
if (creatureData != null)
|
||||
LoadObjectData(creatureData, _creatureInfo);
|
||||
|
||||
if (gameObjectData != null)
|
||||
LoadObjectData(gameObjectData, _gameObjectInfo);
|
||||
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript.LoadObjectData: {0} objects loaded.", _creatureInfo.Count + _gameObjectInfo.Count);
|
||||
}
|
||||
|
||||
void LoadObjectData(ObjectData[] objectData, Dictionary<uint, uint> objectInfo)
|
||||
{
|
||||
foreach (var data in objectData)
|
||||
{
|
||||
Contract.Assert(!objectInfo.ContainsKey(data.entry));
|
||||
objectInfo[data.entry] = data.type;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateMinionState(Creature minion, EncounterState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case EncounterState.NotStarted:
|
||||
if (!minion.IsAlive())
|
||||
minion.Respawn();
|
||||
else if (minion.IsInCombat())
|
||||
minion.GetAI().EnterEvadeMode();
|
||||
break;
|
||||
case EncounterState.InProgress:
|
||||
if (!minion.IsAlive())
|
||||
minion.Respawn();
|
||||
else if (minion.GetVictim() == null)
|
||||
minion.GetAI().DoZoneInCombat();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void UpdateDoorState(GameObject door)
|
||||
{
|
||||
var range = doors.LookupByKey(door.GetEntry());
|
||||
if (range.Empty())
|
||||
return;
|
||||
|
||||
bool open = true;
|
||||
foreach (var info in range)
|
||||
{
|
||||
if (!open)
|
||||
break;
|
||||
|
||||
switch (info.type)
|
||||
{
|
||||
case DoorType.Room:
|
||||
open = (info.bossInfo.state != EncounterState.InProgress);
|
||||
break;
|
||||
case DoorType.Passage:
|
||||
open = (info.bossInfo.state == EncounterState.Done);
|
||||
break;
|
||||
case DoorType.SpawnHole:
|
||||
open = (info.bossInfo.state == EncounterState.InProgress);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
door.SetGoState(open ? GameObjectState.Active : GameObjectState.Ready);
|
||||
}
|
||||
|
||||
public BossInfo GetBossInfo(uint id)
|
||||
{
|
||||
Contract.Assert(id < bosses.Count);
|
||||
return bosses[id];
|
||||
}
|
||||
|
||||
void AddObject(Creature obj, bool add)
|
||||
{
|
||||
if (_creatureInfo.ContainsKey(obj.GetEntry()))
|
||||
AddObject(obj, _creatureInfo[obj.GetEntry()], add);
|
||||
}
|
||||
|
||||
void AddObject(GameObject obj, bool add)
|
||||
{
|
||||
if (_gameObjectInfo.ContainsKey(obj.GetEntry()))
|
||||
AddObject(obj, _gameObjectInfo[obj.GetEntry()], add);
|
||||
}
|
||||
|
||||
void AddObject(WorldObject obj, uint type, bool add)
|
||||
{
|
||||
if (add)
|
||||
_objectGuids[type] = obj.GetGUID();
|
||||
else
|
||||
{
|
||||
var guid = _objectGuids.LookupByKey(type);
|
||||
if (!guid.IsEmpty() && guid == obj.GetGUID())
|
||||
_objectGuids.Remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AddDoor(GameObject door, bool add)
|
||||
{
|
||||
var range = doors.LookupByKey(door.GetEntry());
|
||||
if (range.Empty())
|
||||
return;
|
||||
|
||||
foreach (var data in range)
|
||||
{
|
||||
if (add)
|
||||
data.bossInfo.door[(int)data.type].Add(door.GetGUID());
|
||||
else
|
||||
data.bossInfo.door[(int)data.type].Remove(door.GetGUID());
|
||||
}
|
||||
|
||||
if (add)
|
||||
UpdateDoorState(door);
|
||||
}
|
||||
|
||||
public void AddMinion(Creature minion, bool add)
|
||||
{
|
||||
var minionInfo = minions.LookupByKey(minion.GetEntry());
|
||||
if (minionInfo == null)
|
||||
return;
|
||||
|
||||
if (add)
|
||||
minionInfo.bossInfo.minion.Add(minion.GetGUID());
|
||||
else
|
||||
minionInfo.bossInfo.minion.Remove(minion.GetGUID());
|
||||
}
|
||||
|
||||
public Creature GetCreature(uint type)
|
||||
{
|
||||
return instance.GetCreature(GetObjectGuid(type));
|
||||
}
|
||||
|
||||
public GameObject GetGameObject(uint type)
|
||||
{
|
||||
return instance.GetGameObject(GetObjectGuid(type));
|
||||
}
|
||||
|
||||
public virtual bool SetBossState(uint id, EncounterState state)
|
||||
{
|
||||
if (id < bosses.Count)
|
||||
{
|
||||
BossInfo bossInfo = bosses[id];
|
||||
if (bossInfo.state == EncounterState.ToBeDecided) // loading
|
||||
{
|
||||
bossInfo.state = state;
|
||||
//Log.outError(LogFilter.General "Inialize boss {0} state as {1}.", id, (uint32)state);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bossInfo.state == state)
|
||||
return false;
|
||||
|
||||
if (state == EncounterState.Done)
|
||||
{
|
||||
foreach (var guid in bossInfo.minion)
|
||||
{
|
||||
Creature minion = instance.GetCreature(guid);
|
||||
if (minion)
|
||||
if (minion.isWorldBoss() && minion.IsAlive())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case EncounterState.InProgress:
|
||||
{
|
||||
uint resInterval = GetCombatResurrectionChargeInterval();
|
||||
InitializeCombatResurrections(1, resInterval);
|
||||
SendEncounterStart(1, 9, resInterval, resInterval);
|
||||
break;
|
||||
}
|
||||
case EncounterState.Fail:
|
||||
case EncounterState.Done:
|
||||
ResetCombatResurrections();
|
||||
SendEncounterEnd();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
bossInfo.state = state;
|
||||
SaveToDB();
|
||||
}
|
||||
|
||||
for (uint type = 0; type < (int)DoorType.Max; ++type)
|
||||
{
|
||||
foreach (var guid in bossInfo.door[type])
|
||||
{
|
||||
GameObject door = instance.GetGameObject(guid);
|
||||
if (door)
|
||||
UpdateDoorState(door);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var guid in bossInfo.minion.ToArray())
|
||||
{
|
||||
Creature minion = instance.GetCreature(guid);
|
||||
if (minion)
|
||||
UpdateMinionState(minion, state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool _SkipCheckRequiredBosses(Player player = null)
|
||||
{
|
||||
return player && player.GetSession().HasPermission(RBACPermissions.SkipCheckInstanceRequiredBosses);
|
||||
}
|
||||
|
||||
public virtual void Load(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
OUT_LOAD_INST_DATA_FAIL();
|
||||
return;
|
||||
}
|
||||
|
||||
OUT_LOAD_INST_DATA(data);
|
||||
|
||||
var loadStream = new StringArguments(data);
|
||||
|
||||
if (ReadSaveDataHeaders(loadStream))
|
||||
{
|
||||
ReadSaveDataBossStates(loadStream);
|
||||
ReadSaveDataMore(loadStream);
|
||||
}
|
||||
else
|
||||
OUT_LOAD_INST_DATA_FAIL();
|
||||
|
||||
OUT_LOAD_INST_DATA_COMPLETE();
|
||||
}
|
||||
|
||||
bool ReadSaveDataHeaders(StringArguments data)
|
||||
{
|
||||
foreach (char header in headers)
|
||||
{
|
||||
char buff = data.NextChar();
|
||||
|
||||
if (header != buff)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReadSaveDataBossStates(StringArguments data)
|
||||
{
|
||||
uint bossId = 0;
|
||||
foreach (var i in bosses)
|
||||
{
|
||||
EncounterState buff = (EncounterState)data.NextUInt32();
|
||||
if (buff == EncounterState.InProgress || buff == EncounterState.Fail || buff == EncounterState.Special)
|
||||
buff = EncounterState.NotStarted;
|
||||
|
||||
if (buff < EncounterState.ToBeDecided)
|
||||
SetBossState(bossId++, buff);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string GetSaveData()
|
||||
{
|
||||
OUT_SAVE_INST_DATA();
|
||||
|
||||
StringBuilder saveStream = new StringBuilder();
|
||||
|
||||
WriteSaveDataHeaders(saveStream);
|
||||
WriteSaveDataBossStates(saveStream);
|
||||
WriteSaveDataMore(saveStream);
|
||||
|
||||
OUT_SAVE_INST_DATA_COMPLETE();
|
||||
|
||||
return saveStream.ToString();
|
||||
}
|
||||
|
||||
void WriteSaveDataHeaders(StringBuilder data)
|
||||
{
|
||||
foreach (char header in headers)
|
||||
data.AppendFormat("{0} ", header);
|
||||
}
|
||||
|
||||
void WriteSaveDataBossStates(StringBuilder data)
|
||||
{
|
||||
foreach (BossInfo bossInfo in bosses.Values)
|
||||
data.AppendFormat("{0} ", (uint)bossInfo.state);
|
||||
}
|
||||
|
||||
public void HandleGameObject(ObjectGuid guid, bool open, GameObject go = null)
|
||||
{
|
||||
if (!go)
|
||||
go = instance.GetGameObject(guid);
|
||||
if (go)
|
||||
go.SetGoState(open ? GameObjectState.Active : GameObjectState.Ready);
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript: HandleGameObject failed");
|
||||
}
|
||||
|
||||
public void DoUseDoorOrButton(ObjectGuid uiGuid, uint withRestoreTime = 0, bool useAlternativeState = false)
|
||||
{
|
||||
if (uiGuid.IsEmpty())
|
||||
return;
|
||||
|
||||
GameObject go = instance.GetGameObject(uiGuid);
|
||||
if (go)
|
||||
{
|
||||
if (go.GetGoType() == GameObjectTypes.Door || go.GetGoType() == GameObjectTypes.Button)
|
||||
{
|
||||
if (go.getLootState() == LootState.Ready)
|
||||
go.UseDoorOrButton(withRestoreTime, useAlternativeState);
|
||||
else if (go.getLootState() == LootState.Activated)
|
||||
go.ResetDoorOrButton();
|
||||
}
|
||||
else
|
||||
Log.outError(LogFilter.Scripts, "InstanceScript: DoUseDoorOrButton can't use gameobject entry {0}, because type is {1}.", go.GetEntry(), go.GetGoType());
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript: DoUseDoorOrButton failed");
|
||||
}
|
||||
|
||||
void DoCloseDoorOrButton(ObjectGuid guid)
|
||||
{
|
||||
if (guid.IsEmpty())
|
||||
return;
|
||||
|
||||
GameObject go = instance.GetGameObject(guid);
|
||||
if (go)
|
||||
{
|
||||
if (go.GetGoType() == GameObjectTypes.Door || go.GetGoType() == GameObjectTypes.Button)
|
||||
{
|
||||
if (go.getLootState() == LootState.Activated)
|
||||
go.ResetDoorOrButton();
|
||||
}
|
||||
else
|
||||
Log.outError(LogFilter.Scripts, "InstanceScript: DoCloseDoorOrButton can't use gameobject entry {0}, because type is {1}.", go.GetEntry(), go.GetGoType());
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript: DoCloseDoorOrButton failed");
|
||||
}
|
||||
|
||||
public void DoRespawnGameObject(ObjectGuid guid, uint timeToDespawn)
|
||||
{
|
||||
GameObject go = instance.GetGameObject(guid);
|
||||
if (go)
|
||||
{
|
||||
switch (go.GetGoType())
|
||||
{
|
||||
case GameObjectTypes.Door:
|
||||
case GameObjectTypes.Button:
|
||||
case GameObjectTypes.Trap:
|
||||
case GameObjectTypes.FishingNode:
|
||||
// not expect any of these should ever be handled
|
||||
Log.outError(LogFilter.Scripts, "InstanceScript: DoRespawnGameObject can't respawn gameobject entry {0}, because type is {1}.", go.GetEntry(), go.GetGoType());
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (go.isSpawned())
|
||||
return;
|
||||
|
||||
go.SetRespawnTime((int)timeToDespawn);
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "InstanceScript: DoRespawnGameObject failed");
|
||||
}
|
||||
|
||||
public void DoUpdateWorldState(uint uiStateId, uint uiStateData)
|
||||
{
|
||||
var lPlayers = instance.GetPlayers();
|
||||
|
||||
if (!lPlayers.Empty())
|
||||
{
|
||||
foreach (var player in lPlayers)
|
||||
player.SendUpdateWorldState(uiStateId, uiStateData);
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Scripts, "DoUpdateWorldState attempt send data but no players in map.");
|
||||
}
|
||||
|
||||
// Send Notify to all players in instance
|
||||
void DoSendNotifyToInstance(string format, params object[] args)
|
||||
{
|
||||
var players = instance.GetPlayers();
|
||||
|
||||
if (!players.Empty())
|
||||
{
|
||||
foreach (var player in players)
|
||||
{
|
||||
WorldSession session = player.GetSession();
|
||||
if (session != null)
|
||||
session.SendNotification(format, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update Achievement Criteria for all players in instance
|
||||
public void DoUpdateCriteria(CriteriaTypes type, uint miscValue1 = 0, uint miscValue2 = 0, Unit unit = null)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
|
||||
if (!PlayerList.Empty())
|
||||
foreach (var player in PlayerList)
|
||||
player.UpdateCriteria(type, miscValue1, miscValue2, 0, unit);
|
||||
}
|
||||
|
||||
// Start timed achievement for all players in instance
|
||||
public void DoStartCriteriaTimer(CriteriaTimedTypes type, uint entry)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
|
||||
if (!PlayerList.Empty())
|
||||
foreach (var player in PlayerList)
|
||||
player.StartCriteriaTimer(type, entry);
|
||||
}
|
||||
|
||||
// Stop timed achievement for all players in instance
|
||||
public void DoStopCriteriaTimer(CriteriaTimedTypes type, uint entry)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
|
||||
if (!PlayerList.Empty())
|
||||
foreach (var player in PlayerList)
|
||||
player.RemoveCriteriaTimer(type, entry);
|
||||
}
|
||||
|
||||
// Remove Auras due to Spell on all players in instance
|
||||
public void DoRemoveAurasDueToSpellOnPlayers(uint spell)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
if (!PlayerList.Empty())
|
||||
{
|
||||
foreach (var player in PlayerList)
|
||||
{
|
||||
player.RemoveAurasDueToSpell(spell);
|
||||
Pet pet = player.GetPet();
|
||||
if (pet != null)
|
||||
pet.RemoveAurasDueToSpell(spell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cast spell on all players in instance
|
||||
public void DoCastSpellOnPlayers(uint spell)
|
||||
{
|
||||
var PlayerList = instance.GetPlayers();
|
||||
|
||||
if (!PlayerList.Empty())
|
||||
foreach (var player in PlayerList)
|
||||
player.CastSpell(player, spell, true);
|
||||
}
|
||||
|
||||
public virtual bool CheckAchievementCriteriaMeet(uint criteria_id, Player source, Unit target = null, uint miscvalue1 = 0)
|
||||
{
|
||||
Log.outError(LogFilter.Server, "Achievement system call CheckAchievementCriteriaMeet but instance script for map {0} not have implementation for achievement criteria {1}",
|
||||
instance.GetId(), criteria_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetEntranceLocation(uint worldSafeLocationId)
|
||||
{
|
||||
_entranceId = worldSafeLocationId;
|
||||
if (_temporaryEntranceId != 0)
|
||||
_temporaryEntranceId = 0;
|
||||
}
|
||||
|
||||
public void SendEncounterUnit(EncounterFrameType type, Unit unit = null, byte priority = 0)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EncounterFrameType.Engage:
|
||||
if (unit == null)
|
||||
return;
|
||||
|
||||
InstanceEncounterEngageUnit encounterEngageMessage = new InstanceEncounterEngageUnit();
|
||||
encounterEngageMessage.Unit = unit.GetGUID();
|
||||
encounterEngageMessage.TargetFramePriority = priority;
|
||||
instance.SendToPlayers(encounterEngageMessage);
|
||||
break;
|
||||
case EncounterFrameType.Disengage:
|
||||
if (!unit)
|
||||
return;
|
||||
|
||||
InstanceEncounterDisengageUnit encounterDisengageMessage = new InstanceEncounterDisengageUnit();
|
||||
encounterDisengageMessage.Unit = unit.GetGUID();
|
||||
instance.SendToPlayers(encounterDisengageMessage);
|
||||
break;
|
||||
case EncounterFrameType.UpdatePriority:
|
||||
if (!unit)
|
||||
return;
|
||||
|
||||
InstanceEncounterChangePriority encounterChangePriorityMessage = new InstanceEncounterChangePriority();
|
||||
encounterChangePriorityMessage.Unit = unit.GetGUID();
|
||||
encounterChangePriorityMessage.TargetFramePriority = priority;
|
||||
instance.SendToPlayers(encounterChangePriorityMessage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SendEncounterStart(uint inCombatResCount = 0, uint maxInCombatResCount = 0, uint inCombatResChargeRecovery = 0, uint nextCombatResChargeTime = 0)
|
||||
{
|
||||
InstanceEncounterStart encounterStartMessage = new InstanceEncounterStart();
|
||||
encounterStartMessage.InCombatResCount = inCombatResCount;
|
||||
encounterStartMessage.MaxInCombatResCount = maxInCombatResCount;
|
||||
encounterStartMessage.CombatResChargeRecovery = inCombatResChargeRecovery;
|
||||
encounterStartMessage.NextCombatResChargeTime = nextCombatResChargeTime;
|
||||
|
||||
instance.SendToPlayers(encounterStartMessage);
|
||||
}
|
||||
|
||||
void SendEncounterEnd()
|
||||
{
|
||||
instance.SendToPlayers(new InstanceEncounterEnd());
|
||||
}
|
||||
|
||||
void SendBossKillCredit(uint encounterId)
|
||||
{
|
||||
BossKillCredit bossKillCreditMessage = new BossKillCredit();
|
||||
bossKillCreditMessage.DungeonEncounterID = encounterId;
|
||||
|
||||
instance.SendToPlayers(bossKillCreditMessage);
|
||||
}
|
||||
|
||||
public void UpdateEncounterStateForKilledCreature(uint creatureId, Unit source)
|
||||
{
|
||||
UpdateEncounterState(EncounterCreditType.KillCreature, creatureId, source);
|
||||
}
|
||||
|
||||
public void UpdateEncounterStateForSpellCast(uint spellId, Unit source)
|
||||
{
|
||||
UpdateEncounterState(EncounterCreditType.CastSpell, spellId, source);
|
||||
}
|
||||
|
||||
void UpdateEncounterState(EncounterCreditType type, uint creditEntry, Unit source)
|
||||
{
|
||||
var encounters = Global.ObjectMgr.GetDungeonEncounterList(instance.GetId(), instance.GetDifficultyID());
|
||||
if (encounters.Empty())
|
||||
return;
|
||||
|
||||
uint dungeonId = 0;
|
||||
|
||||
foreach (var encounter in encounters)
|
||||
{
|
||||
if (encounter.creditType == type && encounter.creditEntry == creditEntry)
|
||||
{
|
||||
completedEncounters |= (1u << encounter.dbcEntry.Bit);
|
||||
if (encounter.lastEncounterDungeon != 0)
|
||||
{
|
||||
dungeonId = encounter.lastEncounterDungeon;
|
||||
Log.outDebug(LogFilter.Lfg, "UpdateEncounterState: Instance {0} (instanceId {1}) completed encounter {2}. Credit Dungeon: {3}",
|
||||
instance.GetMapName(), instance.GetInstanceId(), encounter.dbcEntry.Name[Global.WorldMgr.GetDefaultDbcLocale()], dungeonId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dungeonId != 0)
|
||||
{
|
||||
var players = instance.GetPlayers();
|
||||
foreach (var player in players)
|
||||
{
|
||||
Group grp = player.GetGroup();
|
||||
if (grp != null)
|
||||
if (grp.isLFGGroup())
|
||||
{
|
||||
Global.LFGMgr.FinishDungeon(grp.GetGUID(), dungeonId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePhasing()
|
||||
{
|
||||
var players = instance.GetPlayers();
|
||||
foreach (var player in players)
|
||||
player.SendUpdatePhasing();
|
||||
}
|
||||
|
||||
public void UpdateCombatResurrection(uint diff)
|
||||
{
|
||||
if (!_combatResurrectionTimerStarted)
|
||||
return;
|
||||
|
||||
_combatResurrectionTimer -= diff;
|
||||
if (_combatResurrectionTimer <= 0)
|
||||
{
|
||||
AddCombatResurrectionCharge();
|
||||
_combatResurrectionTimerStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeCombatResurrections(byte charges = 1, uint interval = 0)
|
||||
{
|
||||
_combatResurrectionCharges = charges;
|
||||
if (interval == 0)
|
||||
return;
|
||||
|
||||
_combatResurrectionTimer = interval;
|
||||
_combatResurrectionTimerStarted = true;
|
||||
}
|
||||
|
||||
public void AddCombatResurrectionCharge()
|
||||
{
|
||||
++_combatResurrectionCharges;
|
||||
_combatResurrectionTimer = GetCombatResurrectionChargeInterval();
|
||||
_combatResurrectionTimerStarted = true;
|
||||
|
||||
var gainCombatResurrectionCharge = new InstanceEncounterGainCombatResurrectionCharge();
|
||||
gainCombatResurrectionCharge.InCombatResCount = _combatResurrectionCharges;
|
||||
gainCombatResurrectionCharge.CombatResChargeRecovery = _combatResurrectionTimer;
|
||||
instance.SendToPlayers(gainCombatResurrectionCharge);
|
||||
}
|
||||
|
||||
public void UseCombatResurrection()
|
||||
{
|
||||
--_combatResurrectionCharges;
|
||||
|
||||
instance.SendToPlayers(new InstanceEncounterInCombatResurrection());
|
||||
}
|
||||
|
||||
public void ResetCombatResurrections()
|
||||
{
|
||||
_combatResurrectionCharges = 0;
|
||||
_combatResurrectionTimer = 0;
|
||||
_combatResurrectionTimerStarted = false;
|
||||
}
|
||||
|
||||
public uint GetCombatResurrectionChargeInterval()
|
||||
{
|
||||
uint interval = 0;
|
||||
int playerCount = instance.GetPlayers().Count;
|
||||
if (playerCount != 0)
|
||||
interval = (uint)(90 * Time.Minute * Time.InMilliseconds / playerCount);
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
bool InstanceHasScript(WorldObject obj, string scriptName)
|
||||
{
|
||||
InstanceMap instance = obj.GetMap().ToInstanceMap();
|
||||
if (instance != null)
|
||||
return instance.GetScriptName() == scriptName;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void Initialize() { }
|
||||
public virtual void Update(uint diff) { }
|
||||
public virtual void OnPlayerEnter(Player player) { }
|
||||
|
||||
// Return wether server allow two side groups or not
|
||||
public bool ServerAllowsTwoSideGroups() { return WorldConfig.GetBoolValue(WorldCfg.AllowTwoSideInteractionGroup); }
|
||||
|
||||
public EncounterState GetBossState(uint id) { return id < bosses.Count ? bosses[id].state : EncounterState.ToBeDecided; }
|
||||
public List<AreaBoundary> GetBossBoundary(uint id) { return id < bosses.Count ? bosses[id].boundary : null; }
|
||||
|
||||
public virtual bool CheckRequiredBosses(uint bossId, Player player = null) { return true; }
|
||||
|
||||
public void SetCompletedEncountersMask(uint newMask) { completedEncounters = newMask; }
|
||||
|
||||
public uint GetCompletedEncounterMask() { return completedEncounters; }
|
||||
|
||||
// Sets a temporary entrance that does not get saved to db
|
||||
void SetTemporaryEntranceLocation(uint worldSafeLocationId) { _temporaryEntranceId = worldSafeLocationId; }
|
||||
|
||||
// Get's the current entrance id
|
||||
public uint GetEntranceLocation() { return _temporaryEntranceId != 0 ? _temporaryEntranceId : _entranceId; }
|
||||
|
||||
public virtual void FillInitialWorldStates(InitWorldStates data) { }
|
||||
|
||||
public int GetEncounterCount() { return bosses.Count; }
|
||||
|
||||
public byte GetCombatResurrectionCharges() { return _combatResurrectionCharges; }
|
||||
|
||||
public void SetBossNumber(uint number)
|
||||
{
|
||||
for (uint i = 0; i < number; ++i)
|
||||
bosses.Add(i, new BossInfo());
|
||||
}
|
||||
|
||||
public void OUT_SAVE_INST_DATA() { Log.outDebug(LogFilter.Scripts, "Saving Instance Data for Instance {0} (Map {1}, Instance Id {2})", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); }
|
||||
public void OUT_SAVE_INST_DATA_COMPLETE() { Log.outDebug(LogFilter.Scripts, "Saving Instance Data for Instance {0} (Map {1}, Instance Id {2}) completed.", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); }
|
||||
public void OUT_LOAD_INST_DATA(string input) { Log.outDebug(LogFilter.Scripts, "Loading Instance Data for Instance {0} (Map {1}, Instance Id {2}). Input is '{3}'", instance.GetMapName(), instance.GetId(), instance.GetInstanceId(), input); }
|
||||
public void OUT_LOAD_INST_DATA_COMPLETE() { Log.outDebug(LogFilter.Scripts, "Instance Data Load for Instance {0} (Map {1}, Instance Id: {2}) is complete.", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); }
|
||||
public void OUT_LOAD_INST_DATA_FAIL() { Log.outDebug(LogFilter.Scripts, "Unable to load Instance Data for Instance {0} (Map {1}, Instance Id: {2}).", instance.GetMapName(), instance.GetId(), instance.GetInstanceId()); }
|
||||
|
||||
public virtual void ReadSaveDataMore(StringArguments data) { }
|
||||
|
||||
public virtual void WriteSaveDataMore(StringBuilder data) { }
|
||||
|
||||
public Map instance;
|
||||
List<char> headers = new List<char>();
|
||||
Dictionary<uint, BossInfo> bosses = new Dictionary<uint, BossInfo>();
|
||||
MultiMap<uint, DoorInfo> doors = new MultiMap<uint, DoorInfo>();
|
||||
Dictionary<uint, MinionInfo> minions = new Dictionary<uint, MinionInfo>();
|
||||
Dictionary<uint, uint> _creatureInfo = new Dictionary<uint, uint>();
|
||||
Dictionary<uint, uint> _gameObjectInfo = new Dictionary<uint, uint>();
|
||||
Dictionary<uint, ObjectGuid> _objectGuids = new Dictionary<uint, ObjectGuid>();
|
||||
uint completedEncounters;
|
||||
uint _entranceId;
|
||||
uint _temporaryEntranceId;
|
||||
uint _combatResurrectionTimer;
|
||||
byte _combatResurrectionCharges; // the counter for available battle resurrections
|
||||
bool _combatResurrectionTimerStarted;
|
||||
}
|
||||
|
||||
|
||||
public class DoorData
|
||||
{
|
||||
public DoorData(uint _entry, uint _bossid, DoorType _type)
|
||||
{
|
||||
entry = _entry;
|
||||
bossId = _bossid;
|
||||
type = _type;
|
||||
}
|
||||
|
||||
public uint entry;
|
||||
public uint bossId;
|
||||
public DoorType type;
|
||||
}
|
||||
|
||||
public class BossBoundaryEntry
|
||||
{
|
||||
public BossBoundaryEntry(uint bossId, AreaBoundary boundary)
|
||||
{
|
||||
BossId = bossId;
|
||||
Boundary = boundary;
|
||||
}
|
||||
|
||||
public uint BossId;
|
||||
public AreaBoundary Boundary;
|
||||
}
|
||||
|
||||
public class MinionData
|
||||
{
|
||||
public MinionData(uint _entry, uint _bossid)
|
||||
{
|
||||
entry = _entry;
|
||||
bossId = _bossid;
|
||||
}
|
||||
|
||||
public uint entry;
|
||||
public uint bossId;
|
||||
}
|
||||
|
||||
public struct ObjectData
|
||||
{
|
||||
public ObjectData(uint _entry, uint _type)
|
||||
{
|
||||
entry = _entry;
|
||||
type = _type;
|
||||
}
|
||||
|
||||
public uint entry;
|
||||
public uint type;
|
||||
}
|
||||
|
||||
public class BossInfo
|
||||
{
|
||||
public BossInfo()
|
||||
{
|
||||
state = EncounterState.ToBeDecided;
|
||||
for (var i = 0; i < (int)DoorType.Max; ++i)
|
||||
door[i] = new List<ObjectGuid>();
|
||||
}
|
||||
|
||||
public EncounterState state;
|
||||
public List<ObjectGuid>[] door = new List<ObjectGuid>[(int)DoorType.Max];
|
||||
public List<ObjectGuid> minion = new List<ObjectGuid>();
|
||||
public List<AreaBoundary> boundary = new List<AreaBoundary>();
|
||||
}
|
||||
class DoorInfo
|
||||
{
|
||||
public DoorInfo(BossInfo _bossInfo, DoorType _type)
|
||||
{
|
||||
bossInfo = _bossInfo;
|
||||
type = _type;
|
||||
}
|
||||
|
||||
public BossInfo bossInfo;
|
||||
public DoorType type;
|
||||
}
|
||||
class MinionInfo
|
||||
{
|
||||
public MinionInfo(BossInfo _bossInfo)
|
||||
{
|
||||
bossInfo = _bossInfo;
|
||||
}
|
||||
|
||||
public BossInfo bossInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.BattleGrounds;
|
||||
using Game.DataStorage;
|
||||
using Game.Entities;
|
||||
using Game.Garrisons;
|
||||
using Game.Groups;
|
||||
using Game.Scenarios;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class MapInstanced : Map
|
||||
{
|
||||
public MapInstanced(uint id, uint expiry) : base(id, expiry, 0, Difficulty.Normal)
|
||||
{
|
||||
for (uint x = 0; x < MapConst.MaxGrids; ++x)
|
||||
GridMapReference[x] = new uint[MapConst.MaxGrids];
|
||||
}
|
||||
|
||||
public override void InitVisibilityDistance()
|
||||
{
|
||||
if (m_InstancedMaps.Empty())
|
||||
return;
|
||||
//initialize visibility distances for all instance copies
|
||||
foreach (var i in m_InstancedMaps)
|
||||
i.Value.InitVisibilityDistance();
|
||||
}
|
||||
|
||||
public override void Update(uint diff)
|
||||
{
|
||||
base.Update(diff);
|
||||
|
||||
foreach (var i in m_InstancedMaps.ToList())
|
||||
{
|
||||
if (i.Value.CanUnload(diff))
|
||||
{
|
||||
if (!DestroyInstance(i))
|
||||
{
|
||||
//m_unloadTimer
|
||||
}
|
||||
}
|
||||
else
|
||||
i.Value.Update(diff);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DelayedUpdate(uint t_diff)
|
||||
{
|
||||
foreach (var i in m_InstancedMaps)
|
||||
i.Value.DelayedUpdate(t_diff);
|
||||
|
||||
base.DelayedUpdate(t_diff);
|
||||
}
|
||||
|
||||
public override void UnloadAll()
|
||||
{
|
||||
// Unload instanced maps
|
||||
foreach (var i in m_InstancedMaps)
|
||||
i.Value.UnloadAll();
|
||||
|
||||
m_InstancedMaps.Clear();
|
||||
|
||||
base.UnloadAll();
|
||||
}
|
||||
|
||||
public Map CreateInstanceForPlayer(uint mapId, Player player, uint loginInstanceId = 0)
|
||||
{
|
||||
if (GetId() != mapId || player == null)
|
||||
return null;
|
||||
|
||||
Map map = null;
|
||||
uint newInstanceId = 0; // instanceId of the resulting map
|
||||
|
||||
if (IsBattlegroundOrArena())
|
||||
{
|
||||
// instantiate or find existing bg map for player
|
||||
// the instance id is set in Battlegroundid
|
||||
newInstanceId = player.GetBattlegroundId();
|
||||
if (newInstanceId == 0)
|
||||
return null;
|
||||
|
||||
map = Global.MapMgr.FindMap(mapId, newInstanceId);
|
||||
if (map == null)
|
||||
{
|
||||
Battleground bg = player.GetBattleground();
|
||||
if (bg)
|
||||
map = CreateBattleground(newInstanceId, bg);
|
||||
else
|
||||
{
|
||||
player.TeleportToBGEntryPoint();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!IsGarrison())
|
||||
{
|
||||
InstanceBind pBind = player.GetBoundInstance(GetId(), player.GetDifficultyID(GetEntry()));
|
||||
InstanceSave pSave = pBind != null ? pBind.save : null;
|
||||
|
||||
// priority:
|
||||
// 1. player's permanent bind
|
||||
// 2. player's current instance id if this is at login
|
||||
// 3. group's current bind
|
||||
// 4. player's current bind
|
||||
if (pBind == null || !pBind.perm)
|
||||
{
|
||||
if (loginInstanceId != 0) // if the player has a saved instance id on login, we either use this instance or relocate him out (return null)
|
||||
{
|
||||
map = FindInstanceMap(loginInstanceId);
|
||||
return (map && map.GetId() == GetId()) ? map : null; // is this check necessary? or does MapInstanced only find instances of itself?
|
||||
}
|
||||
|
||||
InstanceBind groupBind = null;
|
||||
Group group = player.GetGroup();
|
||||
// use the player's difficulty setting (it may not be the same as the group's)
|
||||
if (group)
|
||||
{
|
||||
groupBind = group.GetBoundInstance(this);
|
||||
if (groupBind != null)
|
||||
{
|
||||
// solo saves should be reset when entering a group's instance
|
||||
player.UnbindInstance(GetId(), player.GetDifficultyID(GetEntry()));
|
||||
pSave = groupBind.save;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pSave != null)
|
||||
{
|
||||
// solo/perm/group
|
||||
newInstanceId = pSave.GetInstanceId();
|
||||
map = FindInstanceMap(newInstanceId);
|
||||
// it is possible that the save exists but the map doesn't
|
||||
if (map == null)
|
||||
map = CreateInstance(newInstanceId, pSave, pSave.GetDifficultyID(), player.GetTeamId());
|
||||
}
|
||||
else
|
||||
{
|
||||
// if no instanceId via group members or instance saves is found
|
||||
// the instance will be created for the first time
|
||||
newInstanceId = Global.MapMgr.GenerateInstanceId();
|
||||
|
||||
Difficulty diff = player.GetGroup() != null ? player.GetGroup().GetDifficultyID(GetEntry()) : player.GetDifficultyID(GetEntry());
|
||||
//Seems it is now possible, but I do not know if it should be allowed
|
||||
map = FindInstanceMap(newInstanceId);
|
||||
if (map == null)
|
||||
map = CreateInstance(newInstanceId, null, diff, player.GetTeamId());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newInstanceId = (uint)player.GetGUID().GetCounter();
|
||||
map = FindInstanceMap(newInstanceId);
|
||||
if (!map)
|
||||
map = CreateGarrison(newInstanceId, player);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
InstanceMap CreateInstance(uint InstanceId, InstanceSave save, Difficulty difficulty, int teamId)
|
||||
{
|
||||
// make sure we have a valid map id
|
||||
MapRecord entry = CliDB.MapStorage.LookupByKey(GetId());
|
||||
if (entry == null)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "CreateInstance: no record for map {0}", GetId());
|
||||
Contract.Assert(false);
|
||||
}
|
||||
InstanceTemplate iTemplate = Global.ObjectMgr.GetInstanceTemplate(GetId());
|
||||
if (iTemplate == null)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "CreateInstance: no instance template for map {0}", GetId());
|
||||
Contract.Assert(false);
|
||||
}
|
||||
|
||||
// some instances only have one difficulty
|
||||
Global.DB2Mgr.GetDownscaledMapDifficultyData(GetId(), ref difficulty);
|
||||
|
||||
Log.outDebug(LogFilter.Maps, "MapInstanced.CreateInstance: {0} map instance {1} for {2} created with difficulty {3}", save != null ? "" : "new ", InstanceId, GetId(), difficulty);
|
||||
|
||||
InstanceMap map = new InstanceMap(GetId(), GetGridExpiry(), InstanceId, difficulty, this);
|
||||
Contract.Assert(map.IsDungeon());
|
||||
|
||||
map.LoadRespawnTimes();
|
||||
map.LoadCorpseData();
|
||||
|
||||
bool load_data = save != null;
|
||||
map.CreateInstanceData(load_data);
|
||||
InstanceScenario instanceScenario = Global.ScenarioMgr.CreateInstanceScenario(map, teamId);
|
||||
if (instanceScenario != null)
|
||||
map.SetInstanceScenario(instanceScenario);
|
||||
|
||||
if (WorldConfig.GetBoolValue(WorldCfg.InstancemapLoadGrids))
|
||||
map.LoadAllCells();
|
||||
|
||||
m_InstancedMaps[InstanceId] = map;
|
||||
return map;
|
||||
}
|
||||
|
||||
BattlegroundMap CreateBattleground(uint InstanceId, Battleground bg)
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MapInstanced.CreateBattleground: map bg {0} for {1} created.", InstanceId, GetId());
|
||||
|
||||
BattlegroundMap map = new BattlegroundMap(GetId(), (uint)GetGridExpiry(), InstanceId, this, Difficulty.None);
|
||||
Contract.Assert(map.IsBattlegroundOrArena());
|
||||
map.SetBG(bg);
|
||||
bg.SetBgMap(map);
|
||||
|
||||
m_InstancedMaps[InstanceId] = map;
|
||||
return map;
|
||||
}
|
||||
|
||||
GarrisonMap CreateGarrison(uint instanceId, Player owner)
|
||||
{
|
||||
GarrisonMap map = new GarrisonMap(GetId(), GetGridExpiry(), instanceId, this, owner.GetGUID());
|
||||
Contract.Assert(map.IsGarrison());
|
||||
|
||||
m_InstancedMaps[instanceId] = map;
|
||||
return map;
|
||||
}
|
||||
|
||||
bool DestroyInstance(KeyValuePair<uint, Map> pair)
|
||||
{
|
||||
pair.Value.RemoveAllPlayers();
|
||||
if (pair.Value.HavePlayers())
|
||||
return false;
|
||||
|
||||
pair.Value.UnloadAll();
|
||||
// should only unload VMaps if this is the last instance and grid unloading is enabled
|
||||
if (m_InstancedMaps.Count <= 1 && WorldConfig.GetBoolValue(WorldCfg.GridUnload))
|
||||
{
|
||||
Global.VMapMgr.unloadMap(pair.Value.GetId());
|
||||
Global.MMapMgr.unloadMap(pair.Value.GetId());
|
||||
// in that case, unload grids of the base map, too
|
||||
// so in the next map creation, (EnsureGridCreated actually) VMaps will be reloaded
|
||||
base.UnloadAll();
|
||||
}
|
||||
|
||||
// Free up the instance id and allow it to be reused for bgs and arenas (other instances are handled in the InstanceSaveMgr)
|
||||
if (pair.Value.IsBattlegroundOrArena())
|
||||
Global.MapMgr.FreeInstanceId(pair.Value.GetInstanceId());
|
||||
|
||||
// erase map
|
||||
m_InstancedMaps.Remove(pair.Key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override EnterState CannotEnter(Player player) { return EnterState.CanEnter; }
|
||||
|
||||
public Map FindInstanceMap(uint instanceId)
|
||||
{
|
||||
return m_InstancedMaps.LookupByKey(instanceId);
|
||||
}
|
||||
|
||||
public void AddGridMapReference(GridCoord p)
|
||||
{
|
||||
++GridMapReference[p.x_coord][p.y_coord];
|
||||
SetUnloadReferenceLock(new GridCoord(63 - p.x_coord, 63 - p.y_coord), true);
|
||||
}
|
||||
|
||||
public void RemoveGridMapReference(GridCoord p)
|
||||
{
|
||||
--GridMapReference[p.x_coord][p.y_coord];
|
||||
if (GridMapReference[p.x_coord][p.y_coord] == 0)
|
||||
SetUnloadReferenceLock(new GridCoord(63 - p.x_coord, 63 - p.y_coord), false);
|
||||
}
|
||||
|
||||
public Dictionary<uint, Map> GetInstancedMaps() { return m_InstancedMaps; }
|
||||
|
||||
Dictionary<uint, Map> m_InstancedMaps = new Dictionary<uint, Map>();
|
||||
uint[][] GridMapReference = new uint[MapConst.MaxGrids][];
|
||||
}
|
||||
|
||||
public class InstanceTemplate
|
||||
{
|
||||
public uint Parent;
|
||||
public uint ScriptId;
|
||||
public bool AllowMount;
|
||||
}
|
||||
|
||||
public class InstanceBind
|
||||
{
|
||||
public InstanceSave save;
|
||||
public bool perm;
|
||||
public BindExtensionState extendState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
public class MMapManager : Singleton<MMapManager>
|
||||
{
|
||||
MMapManager() { }
|
||||
|
||||
const string MAP_FILE_NAME_FORMAT = "{0}/mmaps/{1:D4}.mmap";
|
||||
const string TILE_FILE_NAME_FORMAT = "{0}/mmaps/{1:D4}{2:D2}{3:D2}.mmtile";
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
foreach (MapRecord mapEntry in CliDB.MapStorage.Values)
|
||||
{
|
||||
if (mapEntry.ParentMapID != -1)
|
||||
phaseMapData.Add((uint)mapEntry.ParentMapID, mapEntry.Id);
|
||||
}
|
||||
}
|
||||
|
||||
MMapData GetMMapData(uint mapId)
|
||||
{
|
||||
return loadedMMaps.LookupByKey(mapId);
|
||||
}
|
||||
|
||||
bool loadMapData(uint mapId)
|
||||
{
|
||||
// we already have this map loaded?
|
||||
if (loadedMMaps.ContainsKey(mapId) && loadedMMaps[mapId] != null)
|
||||
return true;
|
||||
|
||||
// load and init dtNavMesh - read parameters from file
|
||||
string filename = string.Format(MAP_FILE_NAME_FORMAT, Global.WorldMgr.GetDataPath(), mapId);
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "Could not open mmap file {0}", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read), Encoding.UTF8))
|
||||
{
|
||||
Detour.dtNavMeshParams Params = new Detour.dtNavMeshParams();
|
||||
Params.orig[0] = reader.ReadSingle();
|
||||
Params.orig[1] = reader.ReadSingle();
|
||||
Params.orig[2] = reader.ReadSingle();
|
||||
|
||||
Params.tileWidth = reader.ReadSingle();
|
||||
Params.tileHeight = reader.ReadSingle();
|
||||
Params.maxTiles = reader.ReadInt32();
|
||||
Params.maxPolys = reader.ReadInt32();
|
||||
|
||||
Detour.dtNavMesh mesh = new Detour.dtNavMesh();
|
||||
if (Detour.dtStatusFailed(mesh.init(Params)))
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap {0:D4} from file {1}", mapId, filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.outInfo(LogFilter.Maps, "MMAP:loadMapData: Loaded {0:D4}.mmap", mapId);
|
||||
|
||||
// store inside our map list
|
||||
loadedMMaps[mapId] = new MMapData(mesh, mapId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
uint packTileID(int x, int y)
|
||||
{
|
||||
return (uint)(x << 16 | y);
|
||||
}
|
||||
|
||||
public bool loadMap(uint mapId, int x, int y)
|
||||
{
|
||||
// make sure the mmap is loaded and ready to load tiles
|
||||
if (!loadMapData(mapId))
|
||||
return false;
|
||||
|
||||
// get this mmap data
|
||||
MMapData mmap = loadedMMaps[mapId];
|
||||
Contract.Assert(mmap.navMesh != null);
|
||||
|
||||
// check if we already have this tile loaded
|
||||
uint packedGridPos = packTileID(x, y);
|
||||
if (mmap.loadedTileRefs.ContainsKey(packedGridPos))
|
||||
return false;
|
||||
|
||||
// load this tile . mmaps/MMMXXYY.mmtile
|
||||
string filename = string.Format(TILE_FILE_NAME_FORMAT, Global.WorldMgr.GetDataPath(), mapId, x, y);
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMAP:loadMap: Could not open mmtile file '{0}'", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
|
||||
{
|
||||
MmapTileHeader fileHeader = reader.ReadStruct<MmapTileHeader>();
|
||||
Array.Reverse(fileHeader.mmapMagic);
|
||||
if (new string(fileHeader.mmapMagic) != MapConst.mmapMagic)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "MMAP:loadMap: Bad header in mmap {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
|
||||
return false;
|
||||
}
|
||||
if (fileHeader.mmapVersion != MapConst.mmapVersion)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "MMAP:loadMap: {0:D4}{1:D2}{2:D2}.mmtile was built with generator v{3}, expected v{4}",
|
||||
mapId, x, y, fileHeader.mmapVersion, MapConst.mmapVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
var bytes = reader.ReadBytes((int)fileHeader.size);
|
||||
|
||||
Detour.dtRawTileData data = new Detour.dtRawTileData();
|
||||
data.FromBytes(bytes, 0);
|
||||
|
||||
ulong tileRef = 0;
|
||||
// memory allocated for data is now managed by detour, and will be deallocated when the tile is removed
|
||||
if (Detour.dtStatusSucceed(mmap.navMesh.addTile(data, 0, 0, ref tileRef)))
|
||||
{
|
||||
mmap.loadedTileRefs.Add(packedGridPos, tileRef);
|
||||
++loadedTiles;
|
||||
Log.outInfo(LogFilter.Maps, "MMAP:loadMap: Loaded mmtile {0:D4}[{1:D2}, {2:D2}]", mapId, x, y);
|
||||
|
||||
var phasedMaps = phaseMapData.LookupByKey(mapId);
|
||||
if (!phasedMaps.Empty())
|
||||
{
|
||||
mmap.AddBaseTile(packedGridPos, data, fileHeader, fileHeader.size);
|
||||
LoadPhaseTiles(phasedMaps, x, y);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.outError(LogFilter.Maps, "MMAP:loadMap: Could not load {0:D4}{1:D2}{2:D2}.mmtile into navmesh", mapId, x, y);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PhasedTile LoadTile(uint mapId, int x, int y)
|
||||
{
|
||||
// load this tile . mmaps/MMMXXYY.mmtile
|
||||
string filename = string.Format(TILE_FILE_NAME_FORMAT, Global.WorldMgr.GetDataPath(), mapId, x, y);
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
// Not all tiles have phased versions, don't flood this msg
|
||||
return null;
|
||||
}
|
||||
PhasedTile pTile = new PhasedTile();
|
||||
|
||||
using (BinaryReader reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
|
||||
{
|
||||
// read header
|
||||
pTile.fileHeader = reader.ReadStruct<MmapTileHeader>();
|
||||
Array.Reverse(pTile.fileHeader.mmapMagic);
|
||||
if (new string(pTile.fileHeader.mmapMagic) != MapConst.mmapMagic)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "MMAP.LoadTile: Bad header in mmap {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pTile.fileHeader.mmapVersion != MapConst.mmapVersion)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "MMAP:LoadTile: {0:D4}{1:D2}{2:D2}.mmtile was built with generator v{3}, expected v{4}", mapId, x, y, pTile.fileHeader.mmapVersion, MapConst.mmapVersion);
|
||||
return null;
|
||||
}
|
||||
|
||||
pTile.data = new Detour.dtRawTileData();
|
||||
pTile.data.FromBytes(reader.ReadBytes((int)pTile.fileHeader.size), 0);
|
||||
|
||||
if (pTile.data.ToBytes().Length == 0)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "MMAP.LoadTile: Bad header or data in mmap {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return pTile;
|
||||
}
|
||||
|
||||
void LoadPhaseTiles(List<uint> phasedMapData, int x, int y)
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMAP.LoadPhaseTiles: Loading phased mmtiles for map {0}, X: {1}, Y: {2}", phasedMapData.FirstOrDefault(), x, y);
|
||||
|
||||
uint packedGridPos = packTileID(x, y);
|
||||
|
||||
foreach (uint phaseMapId in phasedMapData)
|
||||
{
|
||||
PhasedTile data = LoadTile(phaseMapId, x, y);
|
||||
// only a few tiles have terrain swaps, do not write error for them
|
||||
if (data != null)
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMAP.LoadPhaseTiles: Loaded phased {0:D4}{1:D2}{2:D2}.mmtile for root phase map {3}", phaseMapId, x, y, phasedMapData.FirstOrDefault());
|
||||
if (!_phaseTiles.ContainsKey(phaseMapId))
|
||||
_phaseTiles[phaseMapId] = new Dictionary<uint, PhasedTile>();
|
||||
|
||||
_phaseTiles[phaseMapId][packedGridPos] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnloadPhaseTile(List<uint> phasedMapData, int x, int y)
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMAP.UnloadPhaseTile: Unloading phased mmtile for map {0}, X: {1}, Y: {2}", phasedMapData.FirstOrDefault(), x, y);
|
||||
|
||||
uint packedGridPos = packTileID(x, y);
|
||||
|
||||
foreach (uint phaseMapId in phasedMapData.ToList())
|
||||
{
|
||||
var phasedTileDic = _phaseTiles.LookupByKey(phaseMapId);
|
||||
if (phasedTileDic == null)
|
||||
continue;
|
||||
|
||||
var phaseTile = phasedTileDic.LookupByKey(packedGridPos);
|
||||
if (phaseTile != null)
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMAP.UnloadPhaseTile: Unloaded phased {0:D4}{1:D2}{2:D2}.mmtile for root phase map {3}", phaseMapId, x, y, phasedMapData.FirstOrDefault());
|
||||
phasedTileDic.Remove(packedGridPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool unloadMap(uint mapId, uint x, uint y)
|
||||
{
|
||||
// check if we have this map loaded
|
||||
MMapData mmap = GetMMapData(mapId);
|
||||
if (mmap == null)
|
||||
{
|
||||
// file may not exist, therefore not loaded
|
||||
Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh map. {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if we have this tile loaded
|
||||
uint packedGridPos = packTileID((int)x, (int)y);
|
||||
if (!mmap.loadedTileRefs.ContainsKey(packedGridPos))
|
||||
{
|
||||
// file may not exist, therefore not loaded
|
||||
Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh tile. {0:D4}{1:D2}{2:D2}.mmtile", mapId, x, y);
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong tileRef = mmap.loadedTileRefs.LookupByKey(packedGridPos);
|
||||
|
||||
// unload, and mark as non loaded
|
||||
Detour.dtRawTileData data;
|
||||
if (Detour.dtStatusFailed(mmap.navMesh.removeTile(tileRef, out data)))
|
||||
{
|
||||
// this is technically a memory leak
|
||||
// if the grid is later reloaded, dtNavMesh.addTile will return error but no extra memory is used
|
||||
// we cannot recover from this error - assert out
|
||||
Log.outError(LogFilter.Maps, "MMAP:unloadMap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", mapId, x, y);
|
||||
Contract.Assert(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
mmap.loadedTileRefs.Remove(packedGridPos);
|
||||
--loadedTiles;
|
||||
Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded mmtile {0:D4}[{1:D2}, {2:D2}] from {3:D4}", mapId, x, y, mapId);
|
||||
|
||||
var phasedMaps = phaseMapData.LookupByKey(mapId);
|
||||
if (!phasedMaps.Empty())
|
||||
{
|
||||
mmap.DeleteBaseTile(packedGridPos);
|
||||
UnloadPhaseTile(phasedMaps, (int)x, (int)y);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool unloadMap(uint mapId)
|
||||
{
|
||||
if (!loadedMMaps.ContainsKey(mapId))
|
||||
{
|
||||
// file may not exist, therefore not loaded
|
||||
Log.outDebug(LogFilter.Maps, "MMAP:unloadMap: Asked to unload not loaded navmesh map {0:D4}", mapId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// unload all tiles from given map
|
||||
MMapData mmap = loadedMMaps.LookupByKey(mapId);
|
||||
foreach (var i in mmap.loadedTileRefs)
|
||||
{
|
||||
uint x = (i.Key >> 16);
|
||||
uint y = (i.Key & 0x0000FFFF);
|
||||
Detour.dtRawTileData data;
|
||||
if (Detour.dtStatusFailed(mmap.navMesh.removeTile(i.Value, out data)))
|
||||
Log.outError(LogFilter.Maps, "MMAP:unloadMap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", mapId, x, y);
|
||||
else
|
||||
{
|
||||
var phasedMaps = phaseMapData.LookupByKey(mapId);
|
||||
if (!phasedMaps.Empty())
|
||||
{
|
||||
mmap.DeleteBaseTile(i.Key);
|
||||
UnloadPhaseTile(phasedMaps, (int)x, (int)y);
|
||||
}
|
||||
--loadedTiles;
|
||||
Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded mmtile {0:D4} [{1:D2}, {2:D2}] from {3:D4}", mapId, x, y, mapId);
|
||||
}
|
||||
}
|
||||
|
||||
loadedMMaps.Remove(mapId);
|
||||
Log.outInfo(LogFilter.Maps, "MMAP:unloadMap: Unloaded {0:D4}.mmap", mapId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool unloadMapInstance(uint mapId, uint instanceId)
|
||||
{
|
||||
// check if we have this map loaded
|
||||
MMapData mmap = GetMMapData(mapId);
|
||||
if (mmap == null)
|
||||
{
|
||||
// file may not exist, therefore not loaded
|
||||
Log.outDebug(LogFilter.Maps, "MMAP:unloadMapInstance: Asked to unload not loaded navmesh map {0}", mapId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mmap.navMeshQueries.ContainsKey(instanceId))
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId {0} instanceId {1}", mapId, instanceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
mmap.navMeshQueries.Remove(instanceId);
|
||||
Log.outInfo(LogFilter.Maps, "MMAP:unloadMapInstance: Unloaded mapId {0} instanceId {1}", mapId, instanceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Detour.dtNavMesh GetNavMesh(uint mapId, List<uint> swaps)
|
||||
{
|
||||
MMapData mmap = GetMMapData(mapId);
|
||||
if (mmap == null)
|
||||
return null;
|
||||
|
||||
return mmap.GetNavMesh(swaps);
|
||||
}
|
||||
|
||||
public Detour.dtNavMeshQuery GetNavMeshQuery(uint mapId, uint instanceId, List<uint> swaps)
|
||||
{
|
||||
MMapData mmap = GetMMapData(mapId);
|
||||
if (mmap == null)
|
||||
return null;
|
||||
|
||||
if (!mmap.navMeshQueries.ContainsKey(instanceId))
|
||||
{
|
||||
// allocate mesh query
|
||||
Detour.dtNavMeshQuery query = new Detour.dtNavMeshQuery();
|
||||
if (Detour.dtStatusFailed(query.init(mmap.GetNavMesh(swaps), 1024)))
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {0} instanceId {1}", mapId, instanceId);
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.outInfo(LogFilter.Maps, "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId {0} instanceId {1}", mapId, instanceId);
|
||||
mmap.navMeshQueries.Add(instanceId, query);
|
||||
}
|
||||
|
||||
return mmap.navMeshQueries[instanceId];
|
||||
}
|
||||
|
||||
public uint getLoadedTilesCount() { return loadedTiles; }
|
||||
public int getLoadedMapsCount() { return loadedMMaps.Count; }
|
||||
|
||||
public Dictionary<uint, PhasedTile> GetPhaseTileContainer(uint mapId) { return _phaseTiles.LookupByKey(mapId); }
|
||||
|
||||
Dictionary<uint, MMapData> loadedMMaps = new Dictionary<uint, MMapData>();
|
||||
MultiMap<uint, uint> phaseMapData = new MultiMap<uint, uint>();
|
||||
Dictionary<uint, Dictionary<uint, PhasedTile>> _phaseTiles = new Dictionary<uint, Dictionary<uint, PhasedTile>>();
|
||||
uint loadedTiles;
|
||||
}
|
||||
|
||||
public class MMapData
|
||||
{
|
||||
public MMapData(Detour.dtNavMesh mesh, uint mapId)
|
||||
{
|
||||
navMesh = mesh;
|
||||
_mapId = mapId;
|
||||
}
|
||||
|
||||
void RemoveSwap(PhasedTile ptile, uint swap, uint packedXY)
|
||||
{
|
||||
uint x = (packedXY >> 16);
|
||||
uint y = (packedXY & 0x0000FFFF);
|
||||
|
||||
if (!loadedPhasedTiles[swap].Contains(packedXY))
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: mmtile {0:D4}[{1:D2}, {2:D2}] unload skipped, due to not loaded", swap, x, y);
|
||||
return;
|
||||
}
|
||||
Detour.dtMeshHeader header = ptile.data.header;
|
||||
|
||||
Detour.dtRawTileData data;
|
||||
// remove old tile
|
||||
if (Detour.dtStatusFailed(navMesh.removeTile(loadedTileRefs[packedXY], out data)))
|
||||
Log.outError(LogFilter.Maps, "MMapData.RemoveSwap: Could not unload phased {0:D4}{1:D2}{2:D2}.mmtile from navmesh", swap, x, y);
|
||||
else
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Unloaded phased {0:D4}{1:D2}{2:D2}.mmtile from navmesh", swap, x, y);
|
||||
|
||||
// restore base tile
|
||||
ulong loadedRef = 0;
|
||||
if (Detour.dtStatusSucceed(navMesh.addTile(_baseTiles[packedXY].data, 0, 0, ref loadedRef)))
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Loaded base mmtile {0:D4}[{1:D2}, {2:D2}] into {0:D4}[{1:D2}, {2:D2}]", _mapId, x, y, _mapId, header.x, header.y);
|
||||
}
|
||||
else
|
||||
Log.outError(LogFilter.Maps, "MMapData.RemoveSwap: Could not load base {0:D4}{1:D2}{2:D2}.mmtile to navmesh", _mapId, x, y);
|
||||
|
||||
loadedTileRefs[packedXY] = loadedRef;
|
||||
}
|
||||
|
||||
loadedPhasedTiles.Remove(swap, packedXY);
|
||||
|
||||
if (loadedPhasedTiles[swap].Empty())
|
||||
{
|
||||
_activeSwaps.Remove(swap);
|
||||
Log.outDebug(LogFilter.Maps, "MMapData.RemoveSwap: Fully removed swap {0} from map {1}", swap, _mapId);
|
||||
}
|
||||
}
|
||||
|
||||
void AddSwap(PhasedTile ptile, uint swap, uint packedXY)
|
||||
{
|
||||
uint x = (packedXY >> 16);
|
||||
uint y = (packedXY & 0x0000FFFF);
|
||||
|
||||
if (!loadedTileRefs.ContainsKey(packedXY))
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to not loaded base tile on map {3}", swap, x, y, _mapId);
|
||||
return;
|
||||
}
|
||||
if (loadedPhasedTiles[swap].Contains(packedXY))
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: WARNING! phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to already loaded on map {3}", swap, x, y, _mapId);
|
||||
return;
|
||||
}
|
||||
|
||||
Detour.dtMeshHeader header = ptile.data.header;
|
||||
|
||||
Detour.dtMeshTile oldTile = navMesh.getTileByRef(loadedTileRefs[packedXY]);
|
||||
if (oldTile == null)
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: phased mmtile {0:D4}[{1:D2}, {2:D2}] load skipped, due to not loaded base tile ref on map {3}", swap, x, y, _mapId);
|
||||
return;
|
||||
}
|
||||
|
||||
// header xy is based on the swap map's tile set, wich doesn't have all the same tiles as root map, so copy the xy from the orignal header
|
||||
header.x = oldTile.header.x;
|
||||
header.y = oldTile.header.y;
|
||||
|
||||
Detour.dtRawTileData data;
|
||||
// remove old tile
|
||||
if (Detour.dtStatusFailed(navMesh.removeTile(loadedTileRefs[packedXY], out data)))
|
||||
Log.outError(LogFilter.Maps, "MMapData.AddSwap: Could not unload {0:D4}{1:D2}{2:D2}.mmtile from navmesh", _mapId, x, y);
|
||||
else
|
||||
{
|
||||
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: Unloaded {0:D4}{1:D2}{2:D2}.mmtile from navmesh", _mapId, x, y);
|
||||
|
||||
_activeSwaps.Add(swap);
|
||||
loadedPhasedTiles.Add(swap, packedXY);
|
||||
|
||||
// add new swapped tile
|
||||
ulong loadedRef = 0;
|
||||
if (Detour.dtStatusSucceed(navMesh.addTile(ptile.data, 0, 0, ref loadedRef)))
|
||||
Log.outDebug(LogFilter.Maps, "MMapData.AddSwap: Loaded phased mmtile {0:D4}[{1:D2}, {2:D2}] into {0:D4}[{1:D2}, {2:D2}]", swap, x, y, _mapId, header.x, header.y);
|
||||
else
|
||||
Log.outError(LogFilter.Maps, "MMapData.AddSwap: Could not load {0:D4}{1:D2}{2:D2}.mmtile to navmesh", swap, x, y);
|
||||
|
||||
loadedTileRefs[packedXY] = loadedRef;
|
||||
}
|
||||
}
|
||||
|
||||
public Detour.dtNavMesh GetNavMesh(List<uint> swaps)
|
||||
{
|
||||
foreach (uint swap in _activeSwaps)
|
||||
{
|
||||
if (!swaps.Contains(swap)) // swap not active
|
||||
{
|
||||
var ptc = Global.MMapMgr.GetPhaseTileContainer(swap);
|
||||
foreach (var pair in ptc)
|
||||
RemoveSwap(pair.Value, swap, pair.Key); // remove swap
|
||||
}
|
||||
}
|
||||
|
||||
// for each of the calling unit's terrain swaps
|
||||
foreach (uint swap in swaps)
|
||||
{
|
||||
if (!_activeSwaps.Contains(swap)) // swap not active
|
||||
{
|
||||
// for each of the terrain swap's xy tiles
|
||||
var ptc = Global.MMapMgr.GetPhaseTileContainer(swap);
|
||||
if (ptc != null)
|
||||
{
|
||||
foreach (var pair in ptc)
|
||||
AddSwap(pair.Value, swap, pair.Key); // add swap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return navMesh;
|
||||
}
|
||||
|
||||
public void AddBaseTile(uint packedGridPos, Detour.dtRawTileData data, MmapTileHeader fileHeader, uint dataSize)
|
||||
{
|
||||
if (!_baseTiles.ContainsKey(packedGridPos))
|
||||
{
|
||||
PhasedTile phasedTile = new PhasedTile();
|
||||
phasedTile.data = data;
|
||||
phasedTile.fileHeader = fileHeader;
|
||||
phasedTile.dataSize = (int)dataSize;
|
||||
_baseTiles[packedGridPos] = phasedTile;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteBaseTile(uint packedGridPos)
|
||||
{
|
||||
var phaseTile = _baseTiles.LookupByKey(packedGridPos);
|
||||
if (phaseTile != null)
|
||||
{
|
||||
_baseTiles.Remove(packedGridPos);
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<uint, Detour.dtNavMeshQuery> navMeshQueries = new Dictionary<uint, Detour.dtNavMeshQuery>(); // instanceId to query
|
||||
|
||||
public Detour.dtNavMesh navMesh;
|
||||
public Dictionary<uint, ulong> loadedTileRefs = new Dictionary<uint, ulong>();
|
||||
MultiMap<uint, uint> loadedPhasedTiles = new MultiMap<uint, uint>();
|
||||
|
||||
uint _mapId;
|
||||
Dictionary<uint, PhasedTile> _baseTiles = new Dictionary<uint, PhasedTile>();
|
||||
List<uint> _activeSwaps = new List<uint>();
|
||||
}
|
||||
|
||||
public class PhasedTile
|
||||
{
|
||||
public Detour.dtRawTileData data;
|
||||
public MmapTileHeader fileHeader;
|
||||
public int dataSize;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MmapTileHeader
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||
public char[] mmapMagic;
|
||||
public uint dtVersion;
|
||||
public uint mmapVersion;
|
||||
public uint size;
|
||||
public byte usesLiquids;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
public byte[] padding;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.DataStorage;
|
||||
using Game.Groups;
|
||||
using Game.Maps;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Entities
|
||||
{
|
||||
public class MapManager : Singleton<MapManager>
|
||||
{
|
||||
MapManager()
|
||||
{
|
||||
i_gridCleanUpDelay = WorldConfig.GetUIntValue(WorldCfg.IntervalGridclean);
|
||||
i_timer.SetInterval(WorldConfig.GetIntValue(WorldCfg.IntervalMapupdate));
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
//todo needs alot of support for threadsafe.
|
||||
//int num_threads= WorldConfig.GetIntValue(WorldCfg.Numthreads);
|
||||
// Start mtmaps if needed.
|
||||
//if (num_threads > 0)
|
||||
//m_updater = new MapUpdater(WorldConfig.GetIntValue(WorldCfg.Numthreads));
|
||||
}
|
||||
|
||||
public void InitializeVisibilityDistanceInfo()
|
||||
{
|
||||
foreach (var pair in i_maps)
|
||||
pair.Value.InitVisibilityDistance();
|
||||
}
|
||||
|
||||
public Map CreateBaseMap(uint id)
|
||||
{
|
||||
Map map = FindBaseMap(id);
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
|
||||
var entry = CliDB.MapStorage.LookupByKey(id);
|
||||
Contract.Assert(entry != null);
|
||||
|
||||
if (entry.Instanceable())
|
||||
map = new MapInstanced(id, i_gridCleanUpDelay);
|
||||
else
|
||||
{
|
||||
map = new Map(id, i_gridCleanUpDelay, 0, Difficulty.None);
|
||||
map.LoadRespawnTimes();
|
||||
map.LoadCorpseData();
|
||||
}
|
||||
|
||||
i_maps[id] = map;
|
||||
}
|
||||
}
|
||||
Contract.Assert(map != null);
|
||||
return map;
|
||||
}
|
||||
|
||||
public Map FindBaseNonInstanceMap(uint mapId)
|
||||
{
|
||||
Map map = FindBaseMap(mapId);
|
||||
if (map != null && map.Instanceable())
|
||||
return null;
|
||||
return map;
|
||||
}
|
||||
|
||||
public Map CreateMap(uint id, Player player, uint loginInstanceId = 0)
|
||||
{
|
||||
Map m = CreateBaseMap(id);
|
||||
|
||||
if (m != null && m.Instanceable())
|
||||
m = ((MapInstanced)m).CreateInstanceForPlayer(id, player, loginInstanceId);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public Map FindMap(uint mapid, uint instanceId)
|
||||
{
|
||||
Map map = FindBaseMap(mapid);
|
||||
if (map == null)
|
||||
return null;
|
||||
|
||||
if (!map.Instanceable())
|
||||
return instanceId == 0 ? map : null;
|
||||
|
||||
return ((MapInstanced)map).FindInstanceMap(instanceId);
|
||||
}
|
||||
|
||||
public EnterState PlayerCannotEnter(uint mapid, Player player, bool loginCheck = false)
|
||||
{
|
||||
MapRecord entry = CliDB.MapStorage.LookupByKey(mapid);
|
||||
if (entry == null)
|
||||
return EnterState.CannotEnterNoEntry;
|
||||
|
||||
if (!entry.IsDungeon())
|
||||
return EnterState.CanEnter;
|
||||
|
||||
InstanceTemplate instance = Global.ObjectMgr.GetInstanceTemplate(mapid);
|
||||
if (instance == null)
|
||||
return EnterState.CannotEnterUninstancedDungeon;
|
||||
|
||||
Difficulty targetDifficulty, requestedDifficulty;
|
||||
targetDifficulty = requestedDifficulty = player.GetDifficultyID(entry);
|
||||
// Get the highest available difficulty if current setting is higher than the instance allows
|
||||
MapDifficultyRecord mapDiff = Global.DB2Mgr.GetDownscaledMapDifficultyData(entry.Id, ref targetDifficulty);
|
||||
if (mapDiff == null)
|
||||
return EnterState.CannotEnterDifficultyUnavailable;
|
||||
|
||||
//Bypass checks for GMs
|
||||
if (player.IsGameMaster())
|
||||
return EnterState.CanEnter;
|
||||
|
||||
string mapName = entry.MapName[Global.WorldMgr.GetDefaultDbcLocale()];
|
||||
|
||||
Group group = player.GetGroup();
|
||||
if (entry.IsRaid()) // can only enter in a raid group
|
||||
if ((!group || !group.isRaidGroup()) && WorldConfig.GetBoolValue(WorldCfg.InstanceIgnoreRaid))
|
||||
return EnterState.CannotEnterNotInRaid;
|
||||
|
||||
if (!player.IsAlive())
|
||||
{
|
||||
if (player.HasCorpse())
|
||||
{
|
||||
// let enter in ghost mode in instance that connected to inner instance with corpse
|
||||
uint corpseMap = player.GetCorpseLocation().GetMapId();
|
||||
do
|
||||
{
|
||||
if (corpseMap == mapid)
|
||||
break;
|
||||
|
||||
InstanceTemplate corpseInstance = Global.ObjectMgr.GetInstanceTemplate(corpseMap);
|
||||
corpseMap = corpseInstance != null ? corpseInstance.Parent : 0;
|
||||
} while (corpseMap != 0);
|
||||
|
||||
if (corpseMap == 0)
|
||||
return EnterState.CannotEnterCorpseInDifferentInstance;
|
||||
|
||||
Log.outDebug(LogFilter.Maps, "MAP: Player '{0}' has corpse in instance '{1}' and can enter.", player.GetName(), mapName);
|
||||
}
|
||||
else
|
||||
Log.outDebug(LogFilter.Maps, "Map.CanPlayerEnter - player '{0}' is dead but does not have a corpse!", player.GetName());
|
||||
}
|
||||
|
||||
//Get instance where player's group is bound & its map
|
||||
if (!loginCheck && group)
|
||||
{
|
||||
InstanceBind boundInstance = group.GetBoundInstance(entry);
|
||||
if (boundInstance != null && boundInstance.save != null)
|
||||
{
|
||||
Map boundMap = FindMap(mapid, boundInstance.save.GetInstanceId());
|
||||
if (boundMap != null)
|
||||
{
|
||||
EnterState denyReason = boundMap.CannotEnter(player);
|
||||
if (denyReason != 0)
|
||||
return denyReason;
|
||||
}
|
||||
}
|
||||
}
|
||||
// players are only allowed to enter 10 instances per hour
|
||||
if (entry.IsDungeon() && (player.GetGroup() == null || (player.GetGroup() != null && !player.GetGroup().isLFGGroup())))
|
||||
{
|
||||
uint instaceIdToCheck = 0;
|
||||
InstanceSave save = player.GetInstanceSave(mapid);
|
||||
if (save != null)
|
||||
instaceIdToCheck = save.GetInstanceId();
|
||||
|
||||
// instanceId can never be 0 - will not be found
|
||||
if (!player.CheckInstanceCount(instaceIdToCheck) && !player.IsDead())
|
||||
return EnterState.CannotEnterTooManyInstances;
|
||||
}
|
||||
|
||||
//Other requirements
|
||||
if (player.Satisfy(Global.ObjectMgr.GetAccessRequirement(mapid, targetDifficulty), mapid, true))
|
||||
return EnterState.CanEnter;
|
||||
else
|
||||
return EnterState.CannotEnterUnspecifiedReason;
|
||||
}
|
||||
|
||||
public void Update(uint diff)
|
||||
{
|
||||
i_timer.Update(diff);
|
||||
if (!i_timer.Passed())
|
||||
return;
|
||||
|
||||
var time = (uint)i_timer.GetCurrent();
|
||||
foreach (var map in i_maps.Values.ToList())
|
||||
{
|
||||
//if (!m_updater)
|
||||
//m_updater.Enqueue(map, (uint)i_timer.GetCurrent());
|
||||
//else
|
||||
map.Update(time);
|
||||
}
|
||||
|
||||
//List<System.Threading.Tasks.Task> tasks = new List<System.Threading.Tasks.Task>();
|
||||
//foreach (var map in i_maps.Values.ToList())
|
||||
{
|
||||
//tasks.Add(System.Threading.Tasks.Task.Factory.StartNew(() => map.Update(time)));
|
||||
}
|
||||
//System.Threading.Tasks.Task.WaitAll(tasks.ToArray());
|
||||
//if (!m_updater)
|
||||
// m_updater.Wait();
|
||||
|
||||
foreach (var map in i_maps.ToList())
|
||||
map.Value.DelayedUpdate(time);
|
||||
|
||||
i_timer.SetCurrent(0);
|
||||
}
|
||||
|
||||
public bool ExistMapAndVMap(uint mapid, float x, float y)
|
||||
{
|
||||
GridCoord p = GridDefines.ComputeGridCoord(x, y);
|
||||
|
||||
uint gx = 63 - p.x_coord;
|
||||
uint gy = 63 - p.y_coord;
|
||||
|
||||
return Map.ExistMap(mapid, gx, gy) && Map.ExistVMap(mapid, gx, gy);
|
||||
}
|
||||
|
||||
public bool IsValidMAP(uint mapid, bool startUp)
|
||||
{
|
||||
MapRecord mEntry = CliDB.MapStorage.LookupByKey(mapid);
|
||||
|
||||
if (startUp)
|
||||
return mEntry != null ? true : false;
|
||||
else
|
||||
return mEntry != null && (!mEntry.IsDungeon() || Global.ObjectMgr.GetInstanceTemplate(mapid) != null);
|
||||
|
||||
// TODO: add check for Battlegroundtemplate
|
||||
}
|
||||
|
||||
public void UnloadAll()
|
||||
{
|
||||
foreach (var pair in i_maps.ToList())
|
||||
{
|
||||
pair.Value.UnloadAll();
|
||||
i_maps.Remove(pair.Value.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetNumInstances()
|
||||
{
|
||||
uint ret = 0;
|
||||
foreach (var pair in i_maps.ToList())
|
||||
{
|
||||
Map map = pair.Value;
|
||||
if (!map.Instanceable())
|
||||
continue;
|
||||
var maps = ((MapInstanced)map).GetInstancedMaps();
|
||||
foreach (var imap in maps)
|
||||
if (imap.Value.IsDungeon())
|
||||
ret++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public uint GetNumPlayersInInstances()
|
||||
{
|
||||
uint ret = 0;
|
||||
foreach (var pair in i_maps)
|
||||
{
|
||||
Map map = pair.Value;
|
||||
if (!map.Instanceable())
|
||||
continue;
|
||||
var maps = ((MapInstanced)map).GetInstancedMaps();
|
||||
foreach (var imap in maps)
|
||||
if (imap.Value.IsDungeon())
|
||||
ret += (uint)imap.Value.GetPlayers().Count;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void InitInstanceIds()
|
||||
{
|
||||
_nextInstanceId = 1;
|
||||
}
|
||||
|
||||
public void RegisterInstanceId(uint instanceId)
|
||||
{
|
||||
// Allocation and sizing was done in InitInstanceIds()
|
||||
_instanceIds[instanceId] = true;
|
||||
}
|
||||
|
||||
public uint GenerateInstanceId()
|
||||
{
|
||||
uint newInstanceId = _nextInstanceId;
|
||||
|
||||
// Find the lowest available id starting from the current NextInstanceId (which should be the lowest according to the logic in FreeInstanceId()
|
||||
for (uint i = ++_nextInstanceId; i < 0xFFFFFFFF; ++i)
|
||||
{
|
||||
if ((i < _instanceIds.Count && !_instanceIds[i]) || i >= _instanceIds.Count)
|
||||
{
|
||||
_nextInstanceId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newInstanceId == _nextInstanceId)
|
||||
{
|
||||
Log.outError(LogFilter.Maps, "Instance ID overflow!! Can't continue, shutting down server. ");
|
||||
Global.WorldMgr.StopNow();
|
||||
}
|
||||
|
||||
_instanceIds[newInstanceId] = true;
|
||||
|
||||
return newInstanceId;
|
||||
}
|
||||
|
||||
public void FreeInstanceId(uint instanceId)
|
||||
{
|
||||
// If freed instance id is lower than the next id available for new instances, use the freed one instead
|
||||
if (instanceId < _nextInstanceId)
|
||||
SetNextInstanceId(instanceId);
|
||||
|
||||
_instanceIds[instanceId] = false;
|
||||
}
|
||||
|
||||
public void SetGridCleanUpDelay(uint t)
|
||||
{
|
||||
if (t < MapConst.MinGridDelay)
|
||||
i_gridCleanUpDelay = MapConst.MinGridDelay;
|
||||
else
|
||||
i_gridCleanUpDelay = t;
|
||||
}
|
||||
|
||||
public void SetMapUpdateInterval(int t)
|
||||
{
|
||||
if (t < MapConst.MinMapUpdateDelay)
|
||||
t = MapConst.MinMapUpdateDelay;
|
||||
|
||||
i_timer.SetInterval(t);
|
||||
i_timer.Reset();
|
||||
}
|
||||
|
||||
public uint GetNextInstanceId() { return _nextInstanceId; }
|
||||
|
||||
public void SetNextInstanceId(uint nextInstanceId) { _nextInstanceId = nextInstanceId; }
|
||||
|
||||
Map FindBaseMap(uint mapId)
|
||||
{
|
||||
return i_maps.LookupByKey(mapId);
|
||||
}
|
||||
|
||||
uint GetAreaId(uint mapid, float x, float y, float z)
|
||||
{
|
||||
Map m = CreateBaseMap(mapid);
|
||||
return m.GetAreaId(x, y, z);
|
||||
}
|
||||
|
||||
public uint GetZoneId(uint mapid, float x, float y, float z)
|
||||
{
|
||||
Map m = CreateBaseMap(mapid);
|
||||
return m.GetZoneId(x, y, z);
|
||||
}
|
||||
|
||||
public void GetZoneAndAreaId(out uint zoneid, out uint areaid, uint mapid, float x, float y, float z)
|
||||
{
|
||||
Map m = CreateBaseMap(mapid);
|
||||
m.GetZoneAndAreaId(out zoneid, out areaid, x, y, z);
|
||||
}
|
||||
|
||||
public void DoForAllMaps(Action<Map> worker)
|
||||
{
|
||||
foreach (var map in i_maps.Values)
|
||||
{
|
||||
MapInstanced mapInstanced = map.ToMapInstanced();
|
||||
if (mapInstanced)
|
||||
{
|
||||
var instances = mapInstanced.GetInstancedMaps();
|
||||
foreach (var instance in instances.Values)
|
||||
worker(instance);
|
||||
}
|
||||
else
|
||||
worker(map);
|
||||
}
|
||||
}
|
||||
|
||||
public void DoForAllMapsWithMapId(uint mapId, Action<Map> worker)
|
||||
{
|
||||
var map = i_maps.LookupByKey(mapId);
|
||||
if (map != null)
|
||||
{
|
||||
MapInstanced mapInstanced = map.ToMapInstanced();
|
||||
if (mapInstanced)
|
||||
{
|
||||
var instances = mapInstanced.GetInstancedMaps();
|
||||
foreach (var instance in instances)
|
||||
worker(instance.Value);
|
||||
}
|
||||
else
|
||||
worker(map);
|
||||
}
|
||||
}
|
||||
|
||||
public void IncreaseScheduledScriptsCount() { ++_scheduledScripts; }
|
||||
public void DecreaseScheduledScriptCount() { --_scheduledScripts; }
|
||||
public void DecreaseScheduledScriptCount(uint count) { _scheduledScripts -= count; }
|
||||
public bool IsScriptScheduled() { return _scheduledScripts > 0; }
|
||||
|
||||
Dictionary<uint, Map> i_maps = new Dictionary<uint, Map>();
|
||||
IntervalTimer i_timer = new IntervalTimer();
|
||||
uint i_gridCleanUpDelay;
|
||||
Dictionary<uint, bool> _instanceIds = new Dictionary<uint, bool>();
|
||||
uint _nextInstanceId;
|
||||
//MapUpdater m_updater;
|
||||
uint _scheduledScripts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class MapUpdater : IDisposable
|
||||
{
|
||||
public MapUpdater(int numThreads)
|
||||
{
|
||||
_queue = new Queue<MapUpdateRequest>();
|
||||
|
||||
autoResetEvent = new AutoResetEvent[numThreads];
|
||||
for (var i = 0; i < numThreads; ++i)
|
||||
{
|
||||
autoResetEvent[i] = new AutoResetEvent(false);
|
||||
ThreadPool.QueueUserWorkItem(new WaitCallback(OnEnqueue), autoResetEvent[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(Map map, uint diff)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
_queue.Enqueue(new MapUpdateRequest(map, diff));
|
||||
Monitor.PulseAll(_syncLock);
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnEnqueue(object state)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_queue.Count == 0)
|
||||
{
|
||||
((AutoResetEvent)state).Set();
|
||||
Monitor.Wait(_syncLock);
|
||||
}
|
||||
|
||||
if (_queue.Count > 0)
|
||||
{
|
||||
_queue.Dequeue().Call();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
WaitHandle.WaitAll(autoResetEvent);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
Monitor.PulseAll(_syncLock);
|
||||
}
|
||||
}
|
||||
|
||||
private Queue<MapUpdateRequest> _queue;
|
||||
private object _syncLock = new object();
|
||||
private AutoResetEvent[] autoResetEvent;
|
||||
}
|
||||
|
||||
public class MapUpdateRequest
|
||||
{
|
||||
Map m_map;
|
||||
uint m_diff;
|
||||
|
||||
public MapUpdateRequest(Map m, uint d)
|
||||
{
|
||||
m_map = m;
|
||||
m_diff = d;
|
||||
}
|
||||
|
||||
public void Call()
|
||||
{
|
||||
m_map.Update(m_diff);
|
||||
}
|
||||
|
||||
public uint GetId()
|
||||
{
|
||||
return m_map.GetId();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Game.Entities;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
class ObjectGridLoader : Notifier
|
||||
{
|
||||
public ObjectGridLoader(Grid grid, Map map, Cell cell)
|
||||
{
|
||||
i_cell = new Cell(cell);
|
||||
|
||||
i_grid = grid;
|
||||
i_map = map;
|
||||
}
|
||||
|
||||
public void LoadN()
|
||||
{
|
||||
i_creatures = 0;
|
||||
i_gameObjects = 0;
|
||||
i_corpses = 0;
|
||||
i_cell.data.cell_y = 0;
|
||||
for (uint x = 0; x < MapConst.MaxCells; ++x)
|
||||
{
|
||||
i_cell.data.cell_x = x;
|
||||
for (uint y = 0; y < MapConst.MaxCells; ++y)
|
||||
{
|
||||
i_cell.data.cell_y = y;
|
||||
|
||||
var visitor = new Visitor(this, GridMapTypeMask.AllGrid);
|
||||
i_grid.VisitGrid(x, y, visitor);
|
||||
|
||||
ObjectWorldLoader worker = new ObjectWorldLoader(this);
|
||||
visitor = new Visitor(worker, GridMapTypeMask.AllWorld);
|
||||
i_grid.VisitGrid(x, y, visitor);
|
||||
}
|
||||
}
|
||||
Log.outDebug(LogFilter.Maps, "{0} GameObjects, {1} Creatures, and {2} Corpses/Bones loaded for grid {3} on map {4}", i_gameObjects, i_creatures, i_corpses, i_grid.GetGridId(), i_map.GetId());
|
||||
}
|
||||
|
||||
public override void Visit(ICollection<GameObject> objs)
|
||||
{
|
||||
CellCoord cellCoord = i_cell.GetCellCoord();
|
||||
CellObjectGuids cellguids = Global.ObjectMgr.GetCellObjectGuids(i_map.GetId(), (byte)i_map.GetSpawnMode(), cellCoord.GetId());
|
||||
if (cellguids == null)
|
||||
return;
|
||||
|
||||
LoadHelper<GameObject>(cellguids.gameobjects, cellCoord, ref i_gameObjects, i_map);
|
||||
}
|
||||
|
||||
public override void Visit(ICollection<Creature> objs)
|
||||
{
|
||||
CellCoord cellCoord = i_cell.GetCellCoord();
|
||||
CellObjectGuids cellguids = Global.ObjectMgr.GetCellObjectGuids(i_map.GetId(), (byte)i_map.GetSpawnMode(), cellCoord.GetId());
|
||||
if (cellguids == null)
|
||||
return;
|
||||
|
||||
LoadHelper<Creature>(cellguids.creatures, cellCoord, ref i_creatures, i_map);
|
||||
}
|
||||
|
||||
void LoadHelper<T>(SortedSet<ulong> guid_set, CellCoord cell, ref uint count, Map map) where T : WorldObject, new()
|
||||
{
|
||||
foreach (var i_guid in guid_set)
|
||||
{
|
||||
T obj = new T();
|
||||
ulong guid = i_guid;
|
||||
if (!obj.LoadFromDB(guid, map))
|
||||
continue;
|
||||
|
||||
AddObjectHelper(cell, ref count, map, obj);
|
||||
}
|
||||
}
|
||||
|
||||
void AddObjectHelper<T>(CellCoord cellCoord, ref uint count, Map map, T obj) where T : WorldObject
|
||||
{
|
||||
var cell = new Cell(cellCoord);
|
||||
map.AddToGrid(obj, cell);
|
||||
obj.AddToWorld();
|
||||
++count;
|
||||
}
|
||||
|
||||
void AddObjectHelper(CellCoord cellCoord, ref uint count, Map map, Creature obj)
|
||||
{
|
||||
map.AddToGrid(obj, new Cell(cellCoord));
|
||||
obj.AddToWorld();
|
||||
if (obj.isActiveObject())
|
||||
map.AddToActive(obj);
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
public Cell i_cell;
|
||||
public Grid i_grid;
|
||||
public Map i_map;
|
||||
uint i_gameObjects;
|
||||
uint i_creatures;
|
||||
public uint i_corpses;
|
||||
}
|
||||
|
||||
class ObjectWorldLoader : Notifier
|
||||
{
|
||||
public ObjectWorldLoader(ObjectGridLoader gloader)
|
||||
{
|
||||
i_cell = gloader.i_cell;
|
||||
i_map = gloader.i_map;
|
||||
i_grid = gloader.i_grid;
|
||||
i_corpses = gloader.i_corpses;
|
||||
}
|
||||
|
||||
public override void Visit(ICollection<Corpse> objs)
|
||||
{
|
||||
CellCoord cellCoord = i_cell.GetCellCoord();
|
||||
var corpses = i_map.GetCorpsesInCell(cellCoord.GetId());
|
||||
if (corpses != null)
|
||||
{
|
||||
foreach (Corpse corpse in corpses)
|
||||
{
|
||||
corpse.AddToWorld();
|
||||
var cell = i_grid.GetGridCell(i_cell.GetCellX(), i_cell.GetCellY());
|
||||
if (corpse.IsWorldObject())
|
||||
{
|
||||
i_map.AddToGrid(corpse, new Cell(cellCoord));
|
||||
cell.AddWorldObject(corpse);
|
||||
}
|
||||
else
|
||||
cell.AddGridObject(corpse);
|
||||
|
||||
++i_corpses;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cell i_cell;
|
||||
Map i_map;
|
||||
Grid i_grid;
|
||||
|
||||
public uint i_corpses;
|
||||
}
|
||||
|
||||
//Stop the creatures before unloading the NGrid
|
||||
class ObjectGridStoper : Notifier
|
||||
{
|
||||
public override void Visit(ICollection<Creature> objs)
|
||||
{
|
||||
// stop any fights at grid de-activation and remove dynobjects/areatriggers created at cast by creatures
|
||||
foreach (var creature in objs)
|
||||
{
|
||||
creature.RemoveAllDynObjects();
|
||||
creature.RemoveAllAreaTriggers();
|
||||
|
||||
if (creature.IsInCombat())
|
||||
{
|
||||
creature.CombatStop();
|
||||
creature.DeleteThreatList();
|
||||
if (creature.IsAIEnabled)
|
||||
creature.GetAI().EnterEvadeMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Move the foreign creatures back to respawn positions before unloading the NGrid
|
||||
class ObjectGridEvacuator : Notifier
|
||||
{
|
||||
public override void Visit(ICollection<Creature> objs)
|
||||
{
|
||||
foreach (var creature in objs)
|
||||
{
|
||||
// creature in unloading grid can have respawn point in another grid
|
||||
// if it will be unloaded then it will not respawn in original grid until unload/load original grid
|
||||
// move to respawn point to prevent this case. For player view in respawn grid this will be normal respawn.
|
||||
creature.GetMap().CreatureRespawnRelocation(creature, true);
|
||||
}
|
||||
}
|
||||
public override void Visit(ICollection<GameObject> objs)
|
||||
{
|
||||
foreach (var go in objs)
|
||||
{
|
||||
// gameobject in unloading grid can have respawn point in another grid
|
||||
// if it will be unloaded then it will not respawn in original grid until unload/load original grid
|
||||
// move to respawn point to prevent this case. For player view in respawn grid this will be normal respawn.
|
||||
go.GetMap().GameObjectRespawnRelocation(go, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Clean up and remove from world
|
||||
class ObjectGridCleaner : Notifier
|
||||
{
|
||||
public override void Visit(ICollection<WorldObject> objs)
|
||||
{
|
||||
foreach (var obj in objs)
|
||||
{
|
||||
if (obj.IsTypeId(TypeId.Player))
|
||||
continue;
|
||||
|
||||
obj.CleanupsBeforeDelete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Delete objects before deleting NGrid
|
||||
class ObjectGridUnloader : Notifier
|
||||
{
|
||||
public override void Visit(ICollection<WorldObject> objs)
|
||||
{
|
||||
foreach (var obj in objs)
|
||||
{
|
||||
if (obj.IsTypeId(TypeId.Player) || obj.IsTypeId(TypeId.Corpse))
|
||||
continue;
|
||||
|
||||
// if option set then object already saved at this moment
|
||||
if (!WorldConfig.GetBoolValue(WorldCfg.SaveRespawnTimeImmediately))
|
||||
obj.SaveRespawnTime();
|
||||
//Some creatures may summon other temp summons in CleanupsBeforeDelete()
|
||||
//So we need this even after cleaner (maybe we can remove cleaner)
|
||||
//Example: Flame Leviathan Turret 33139 is summoned when a creature is deleted
|
||||
//TODO: Check if that script has the correct logic. Do we really need to summons something before deleting?
|
||||
obj.CleanupsBeforeDelete();
|
||||
obj.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using Framework.GameMath;
|
||||
using Game.DataStorage;
|
||||
using Game.Entities;
|
||||
using Game.Movement;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class TransportManager : Singleton<TransportManager>
|
||||
{
|
||||
TransportManager() { }
|
||||
|
||||
void Unload()
|
||||
{
|
||||
_transportTemplates.Clear();
|
||||
}
|
||||
|
||||
public void LoadTransportTemplates()
|
||||
{
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
SQLResult result = DB.World.Query("SELECT entry FROM gameobject_template WHERE type = 15 ORDER BY entry ASC");
|
||||
|
||||
if (result.IsEmpty())
|
||||
{
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 transports templates. DB table `gameobject_template` has no transports!");
|
||||
return;
|
||||
}
|
||||
|
||||
uint count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
uint entry = result.Read<uint>(0);
|
||||
GameObjectTemplate goInfo = Global.ObjectMgr.GetGameObjectTemplate(entry);
|
||||
if (goInfo == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Transport {0} has no associated GameObjectTemplate from `gameobject_template` , skipped.", entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (goInfo.MoTransport.taxiPathID >= CliDB.TaxiPathNodesByPath.Keys.Max())
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Transport {0} (name: {1}) has an invalid path specified in `gameobject_template`.`data0` ({2}) field, skipped.", entry, goInfo.name, goInfo.MoTransport.taxiPathID);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (goInfo.MoTransport.taxiPathID == 0)
|
||||
continue;
|
||||
|
||||
// paths are generated per template, saves us from generating it again in case of instanced transports
|
||||
TransportTemplate transport = new TransportTemplate();
|
||||
transport.entry = entry;
|
||||
GeneratePath(goInfo, transport);
|
||||
|
||||
_transportTemplates[entry] = transport;
|
||||
|
||||
// transports in instance are only on one map
|
||||
if (transport.inInstance)
|
||||
_instanceTransports.Add(transport.mapsUsed.First(), entry);
|
||||
|
||||
++count;
|
||||
} while (result.NextRow());
|
||||
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} transports in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
public void LoadTransportAnimationAndRotation()
|
||||
{
|
||||
foreach (TransportAnimationRecord anim in CliDB.TransportAnimationStorage.Values)
|
||||
AddPathNodeToTransport(anim.TransportID, anim.TimeIndex, anim);
|
||||
|
||||
foreach (TransportRotationRecord rot in CliDB.TransportRotationStorage.Values)
|
||||
AddPathRotationToTransport(rot.TransportID, rot.TimeIndex, rot);
|
||||
}
|
||||
|
||||
void GeneratePath(GameObjectTemplate goInfo, TransportTemplate transport)
|
||||
{
|
||||
uint pathId = goInfo.MoTransport.taxiPathID;
|
||||
var path = CliDB.TaxiPathNodesByPath[pathId];
|
||||
List<KeyFrame> keyFrames = transport.keyFrames;
|
||||
List<Vector3> splinePath = new List<Vector3>();
|
||||
List<Vector3> allPoints = new List<Vector3>();
|
||||
bool mapChange = false;
|
||||
|
||||
for (uint i = 0; i < path.Length; ++i)
|
||||
allPoints.Add(new Vector3(path[i].Loc.X, path[i].Loc.Y, path[i].Loc.Z));
|
||||
|
||||
// Add extra points to allow derivative calculations for all path nodes
|
||||
allPoints.Insert(0, allPoints.First().lerp(allPoints[1], -0.2f));
|
||||
allPoints.Add(allPoints.Last().lerp(allPoints[allPoints.Count - 2], -0.2f));
|
||||
allPoints.Add(allPoints.Last().lerp(allPoints[allPoints.Count - 2], -1.0f));
|
||||
|
||||
SplineRawInitializer initer = new SplineRawInitializer(allPoints);
|
||||
Spline orientationSpline = new Spline();
|
||||
orientationSpline.init_spline_custom(initer);
|
||||
orientationSpline.initLengths();
|
||||
|
||||
for (uint i = 0; i < path.Length; ++i)
|
||||
{
|
||||
if (!mapChange)
|
||||
{
|
||||
var node_i = path[i];
|
||||
if (i != path.Length - 1 && (node_i.Flags.HasAnyFlag(TaxiPathNodeFlags.Teleport) || node_i.MapID != path[i + 1].MapID))
|
||||
{
|
||||
keyFrames.Last().Teleport = true;
|
||||
mapChange = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyFrame k = new KeyFrame(node_i);
|
||||
Vector3 h;
|
||||
orientationSpline.Evaluate_Derivative((int)(i + 1), 0.0f, out h);
|
||||
k.InitialOrientation = Position.NormalizeOrientation((float)Math.Atan2(h.Y, h.X) + MathFunctions.PI);
|
||||
|
||||
keyFrames.Add(k);
|
||||
splinePath.Add(new Vector3(node_i.Loc.X, node_i.Loc.Y, node_i.Loc.Z));
|
||||
if (!transport.mapsUsed.Contains(k.Node.MapID))
|
||||
transport.mapsUsed.Add(k.Node.MapID);
|
||||
}
|
||||
}
|
||||
else
|
||||
mapChange = false;
|
||||
}
|
||||
|
||||
if (splinePath.Count >= 2)
|
||||
{
|
||||
// Remove special catmull-rom spline points
|
||||
if (!keyFrames.First().IsStopFrame() && keyFrames.First().Node.ArrivalEventID == 0 && keyFrames.First().Node.DepartureEventID == 0)
|
||||
{
|
||||
splinePath.RemoveAt(0);
|
||||
keyFrames.RemoveAt(0);
|
||||
}
|
||||
if (!keyFrames.Last().IsStopFrame() && keyFrames.Last().Node.ArrivalEventID == 0 && keyFrames.Last().Node.DepartureEventID == 0)
|
||||
{
|
||||
splinePath.RemoveAt(splinePath.Count - 1);
|
||||
keyFrames.RemoveAt(keyFrames.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
Contract.Assert(!keyFrames.Empty());
|
||||
|
||||
if (transport.mapsUsed.Count > 1)
|
||||
{
|
||||
foreach (var mapId in transport.mapsUsed)
|
||||
Contract.Assert(!CliDB.MapStorage.LookupByKey(mapId).Instanceable());
|
||||
|
||||
transport.inInstance = false;
|
||||
}
|
||||
else
|
||||
transport.inInstance = CliDB.MapStorage.LookupByKey(transport.mapsUsed.First()).Instanceable();
|
||||
|
||||
// last to first is always "teleport", even for closed paths
|
||||
keyFrames.Last().Teleport = true;
|
||||
|
||||
float speed = goInfo.MoTransport.moveSpeed;
|
||||
float accel = goInfo.MoTransport.accelRate;
|
||||
float accel_dist = 0.5f * speed * speed / accel;
|
||||
|
||||
transport.accelTime = speed / accel;
|
||||
transport.accelDist = accel_dist;
|
||||
|
||||
int firstStop = -1;
|
||||
int lastStop = -1;
|
||||
|
||||
// first cell is arrived at by teleportation :S
|
||||
keyFrames[0].DistFromPrev = 0;
|
||||
keyFrames[0].Index = 1;
|
||||
if (keyFrames[0].IsStopFrame())
|
||||
{
|
||||
firstStop = 0;
|
||||
lastStop = 0;
|
||||
}
|
||||
|
||||
// find the rest of the distances between key points
|
||||
// Every path segment has its own spline
|
||||
int start = 0;
|
||||
for (int i = 1; i < keyFrames.Count; ++i)
|
||||
{
|
||||
if (keyFrames[i - 1].Teleport || i + 1 == keyFrames.Count)
|
||||
{
|
||||
int extra = !keyFrames[i - 1].Teleport ? 1 : 0;
|
||||
Spline spline = new Spline();
|
||||
spline.Init_Spline(splinePath.Skip(start).ToArray(), i - start + extra, Spline.EvaluationMode.Catmullrom);
|
||||
spline.initLengths();
|
||||
for (int j = start; j < i + extra; ++j)
|
||||
{
|
||||
keyFrames[j].Index = (uint)(j - start + 1);
|
||||
keyFrames[j].DistFromPrev = spline.length(j - start, j + 1 - start);
|
||||
if (j > 0)
|
||||
keyFrames[j - 1].NextDistFromPrev = keyFrames[j].DistFromPrev;
|
||||
keyFrames[j].Spline = spline;
|
||||
}
|
||||
|
||||
if (keyFrames[i - 1].Teleport)
|
||||
{
|
||||
keyFrames[i].Index = (uint)(i - start + 1);
|
||||
keyFrames[i].DistFromPrev = 0.0f;
|
||||
keyFrames[i - 1].NextDistFromPrev = 0.0f;
|
||||
keyFrames[i].Spline = spline;
|
||||
}
|
||||
|
||||
start = i;
|
||||
}
|
||||
if (keyFrames[i].IsStopFrame())
|
||||
{
|
||||
// remember first stop frame
|
||||
if (firstStop == -1)
|
||||
firstStop = i;
|
||||
lastStop = i;
|
||||
}
|
||||
}
|
||||
|
||||
keyFrames.Last().NextDistFromPrev = keyFrames.First().DistFromPrev;
|
||||
|
||||
if (firstStop == -1 || lastStop == -1)
|
||||
firstStop = lastStop = 0;
|
||||
|
||||
// at stopping keyframes, we define distSinceStop == 0,
|
||||
// and distUntilStop is to the next stopping keyframe.
|
||||
// this is required to properly handle cases of two stopping frames in a row (yes they do exist)
|
||||
float tmpDist = 0.0f;
|
||||
for (int i = 0; i < keyFrames.Count; ++i)
|
||||
{
|
||||
int j = (i + lastStop) % keyFrames.Count;
|
||||
if (keyFrames[j].IsStopFrame() || j == lastStop)
|
||||
tmpDist = 0.0f;
|
||||
else
|
||||
tmpDist += keyFrames[j].DistFromPrev;
|
||||
keyFrames[j].DistSinceStop = tmpDist;
|
||||
}
|
||||
|
||||
tmpDist = 0.0f;
|
||||
for (int i = (keyFrames.Count - 1); i >= 0; i--)
|
||||
{
|
||||
int j = (i + firstStop) % keyFrames.Count;
|
||||
tmpDist += keyFrames[(j + 1) % keyFrames.Count].DistFromPrev;
|
||||
keyFrames[j].DistUntilStop = tmpDist;
|
||||
if (keyFrames[j].IsStopFrame() || j == firstStop)
|
||||
tmpDist = 0.0f;
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyFrames.Count; ++i)
|
||||
{
|
||||
float total_dist = keyFrames[i].DistSinceStop + keyFrames[i].DistUntilStop;
|
||||
if (total_dist < 2 * accel_dist) // won't reach full speed
|
||||
{
|
||||
if (keyFrames[i].DistSinceStop < keyFrames[i].DistUntilStop) // is still accelerating
|
||||
{
|
||||
// calculate accel+brake time for this short segment
|
||||
float segment_time = 2.0f * (float)Math.Sqrt((keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / accel);
|
||||
// substract acceleration time
|
||||
keyFrames[i].TimeTo = segment_time - (float)Math.Sqrt(2 * keyFrames[i].DistSinceStop / accel);
|
||||
}
|
||||
else // slowing down
|
||||
keyFrames[i].TimeTo = (float)Math.Sqrt(2 * keyFrames[i].DistUntilStop / accel);
|
||||
}
|
||||
else if (keyFrames[i].DistSinceStop < accel_dist) // still accelerating (but will reach full speed)
|
||||
{
|
||||
// calculate accel + cruise + brake time for this long segment
|
||||
float segment_time = (keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / speed + (speed / accel);
|
||||
// substract acceleration time
|
||||
keyFrames[i].TimeTo = segment_time - (float)Math.Sqrt(2 * keyFrames[i].DistSinceStop / accel);
|
||||
}
|
||||
else if (keyFrames[i].DistUntilStop < accel_dist) // already slowing down (but reached full speed)
|
||||
keyFrames[i].TimeTo = (float)Math.Sqrt(2 * keyFrames[i].DistUntilStop / accel);
|
||||
else // at full speed
|
||||
keyFrames[i].TimeTo = (keyFrames[i].DistUntilStop / speed) + (0.5f * speed / accel);
|
||||
}
|
||||
|
||||
// calculate tFrom times from tTo times
|
||||
float segmentTime = 0.0f;
|
||||
for (int i = 0; i < keyFrames.Count; ++i)
|
||||
{
|
||||
int j = (i + lastStop) % keyFrames.Count;
|
||||
if (keyFrames[j].IsStopFrame() || j == lastStop)
|
||||
segmentTime = keyFrames[j].TimeTo;
|
||||
keyFrames[j].TimeFrom = segmentTime - keyFrames[j].TimeTo;
|
||||
}
|
||||
|
||||
// calculate path times
|
||||
keyFrames[0].ArriveTime = 0;
|
||||
float curPathTime = 0.0f;
|
||||
if (keyFrames[0].IsStopFrame())
|
||||
{
|
||||
curPathTime = keyFrames[0].Node.Delay;
|
||||
keyFrames[0].DepartureTime = (uint)(curPathTime * Time.InMilliseconds);
|
||||
}
|
||||
|
||||
for (int i = 1; i < keyFrames.Count; ++i)
|
||||
{
|
||||
curPathTime += keyFrames[i - 1].TimeTo;
|
||||
if (keyFrames[i].IsStopFrame())
|
||||
{
|
||||
keyFrames[i].ArriveTime = (uint)(curPathTime * Time.InMilliseconds);
|
||||
keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
|
||||
curPathTime += keyFrames[i].Node.Delay;
|
||||
keyFrames[i].DepartureTime = (uint)(curPathTime * Time.InMilliseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
curPathTime -= keyFrames[i].TimeTo;
|
||||
keyFrames[i].ArriveTime = (uint)(curPathTime * Time.InMilliseconds);
|
||||
keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
|
||||
keyFrames[i].DepartureTime = keyFrames[i].ArriveTime;
|
||||
}
|
||||
}
|
||||
keyFrames.Last().NextArriveTime = keyFrames.Last().DepartureTime;
|
||||
|
||||
transport.pathTime = keyFrames.Last().DepartureTime;
|
||||
if (transport.pathTime == 0)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPathNodeToTransport(uint transportEntry, uint timeSeg, TransportAnimationRecord node)
|
||||
{
|
||||
TransportAnimation animNode = new TransportAnimation();
|
||||
if (animNode.TotalTime < timeSeg)
|
||||
animNode.TotalTime = timeSeg;
|
||||
|
||||
animNode.Path[timeSeg] = node;
|
||||
|
||||
_transportAnimations[transportEntry] = animNode;
|
||||
}
|
||||
|
||||
public Transport CreateTransport(uint entry, ulong guid = 0, Map map = null, uint phaseid = 0, uint phasegroup = 0)
|
||||
{
|
||||
// instance case, execute GetGameObjectEntry hook
|
||||
if (map != null)
|
||||
{
|
||||
// SetZoneScript() is called after adding to map, so fetch the script using map
|
||||
if (map.IsDungeon())
|
||||
{
|
||||
InstanceScript instance = ((InstanceMap)map).GetInstanceScript();
|
||||
if (instance != null)
|
||||
entry = instance.GetGameObjectEntry(0, entry);
|
||||
}
|
||||
|
||||
if (entry == 0)
|
||||
return null;
|
||||
}
|
||||
|
||||
TransportTemplate tInfo = GetTransportTemplate(entry);
|
||||
if (tInfo == null)
|
||||
{
|
||||
Log.outError(LogFilter.Sql, "Transport {0} will not be loaded, `transport_template` missing", entry);
|
||||
return null;
|
||||
}
|
||||
|
||||
// create transport...
|
||||
Transport trans = new Transport();
|
||||
|
||||
// ...at first waypoint
|
||||
TaxiPathNodeRecord startNode = tInfo.keyFrames.First().Node;
|
||||
uint mapId = startNode.MapID;
|
||||
float x = startNode.Loc.X;
|
||||
float y = startNode.Loc.Y;
|
||||
float z = startNode.Loc.Z;
|
||||
float o = tInfo.keyFrames.First().InitialOrientation;
|
||||
|
||||
// initialize the gameobject base
|
||||
ulong guidLow = guid != 0 ? guid : map.GenerateLowGuid(HighGuid.Transport);
|
||||
if (!trans.Create(guidLow, entry, mapId, x, y, z, o, 255))
|
||||
return null;
|
||||
|
||||
if (phaseid != 0)
|
||||
trans.SetInPhase(phaseid, false, true);
|
||||
|
||||
if (phasegroup != 0)
|
||||
foreach (var ph in Global.DB2Mgr.GetPhasesForGroup(phasegroup))
|
||||
trans.SetInPhase(ph, false, true);
|
||||
|
||||
MapRecord mapEntry = CliDB.MapStorage.LookupByKey(mapId);
|
||||
if (mapEntry != null)
|
||||
{
|
||||
if (mapEntry.Instanceable() != tInfo.inInstance)
|
||||
{
|
||||
Log.outError(LogFilter.Transport, "Transport {0} (name: {1}) attempted creation in instance map (id: {2}) but it is not an instanced transport!", entry, trans.GetName(), mapId);
|
||||
//return null;
|
||||
}
|
||||
}
|
||||
|
||||
// use preset map for instances (need to know which instance)
|
||||
trans.SetMap(map != null ? map : Global.MapMgr.CreateMap(mapId, null));
|
||||
if (map != null && map.IsDungeon())
|
||||
trans.m_zoneScript = map.ToInstanceMap().GetInstanceScript();
|
||||
|
||||
// Passengers will be loaded once a player is near
|
||||
|
||||
Global.ObjAccessor.AddObject(trans);
|
||||
trans.GetMap().AddToMap(trans);
|
||||
return trans;
|
||||
}
|
||||
|
||||
public void SpawnContinentTransports()
|
||||
{
|
||||
if (_transportTemplates.Empty())
|
||||
return;
|
||||
|
||||
uint oldMSTime = Time.GetMSTime();
|
||||
|
||||
SQLResult result = DB.World.Query("SELECT guid, entry, phaseid, phasegroup FROM transports");
|
||||
|
||||
uint count = 0;
|
||||
if (!result.IsEmpty())
|
||||
{
|
||||
do
|
||||
{
|
||||
ulong guid = result.Read<ulong>(0);
|
||||
uint entry = result.Read<uint>(1);
|
||||
uint phaseid = result.Read<uint>(2);
|
||||
uint phasegroup = result.Read<uint>(3);
|
||||
|
||||
TransportTemplate tInfo = GetTransportTemplate(entry);
|
||||
if (tInfo != null)
|
||||
if (!tInfo.inInstance)
|
||||
if (CreateTransport(entry, guid, null, phaseid, phasegroup))
|
||||
++count;
|
||||
|
||||
} while (result.NextRow());
|
||||
}
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Spawned {0} continent transports in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
public void CreateInstanceTransports(Map map)
|
||||
{
|
||||
var mapTransports = _instanceTransports.LookupByKey(map.GetId());
|
||||
|
||||
// no transports here
|
||||
if (mapTransports.Empty())
|
||||
return;
|
||||
|
||||
// create transports
|
||||
foreach (var entry in mapTransports)
|
||||
CreateTransport(entry, 0, map);
|
||||
}
|
||||
|
||||
public TransportTemplate GetTransportTemplate(uint entry)
|
||||
{
|
||||
return _transportTemplates.LookupByKey(entry);
|
||||
}
|
||||
|
||||
public TransportAnimation GetTransportAnimInfo(uint entry)
|
||||
{
|
||||
return _transportAnimations.LookupByKey(entry);
|
||||
}
|
||||
|
||||
public void AddPathRotationToTransport(uint transportEntry, uint timeSeg, TransportRotationRecord node)
|
||||
{
|
||||
if (!_transportAnimations.ContainsKey(transportEntry))
|
||||
_transportAnimations[transportEntry] = new TransportAnimation();
|
||||
|
||||
_transportAnimations[transportEntry].Rotations[timeSeg] = node;
|
||||
}
|
||||
|
||||
Dictionary<uint, TransportTemplate> _transportTemplates = new Dictionary<uint, TransportTemplate>();
|
||||
MultiMap<uint, uint> _instanceTransports = new MultiMap<uint, uint>();
|
||||
Dictionary<uint, TransportAnimation> _transportAnimations = new Dictionary<uint, TransportAnimation>();
|
||||
}
|
||||
|
||||
public class SplineRawInitializer
|
||||
{
|
||||
public SplineRawInitializer(List<Vector3> points)
|
||||
{
|
||||
_points = points;
|
||||
}
|
||||
|
||||
public void Initialize(ref Spline.EvaluationMode mode, ref bool cyclic, ref Vector3[] points, ref int lo, ref int hi)
|
||||
{
|
||||
mode = Spline.EvaluationMode.Catmullrom;
|
||||
cyclic = false;
|
||||
points = new Vector3[_points.Count];
|
||||
|
||||
for(var i = 0; i < _points.Count; ++i)
|
||||
points[i] = _points[i];
|
||||
|
||||
lo = 1;
|
||||
hi = points.Length - 2;
|
||||
}
|
||||
|
||||
List<Vector3> _points;
|
||||
}
|
||||
|
||||
public class KeyFrame
|
||||
{
|
||||
public KeyFrame(TaxiPathNodeRecord _node)
|
||||
{
|
||||
Node = _node;
|
||||
DistSinceStop = -1.0f;
|
||||
DistUntilStop = -1.0f;
|
||||
DistFromPrev = -1.0f;
|
||||
TimeFrom = 0.0f;
|
||||
TimeTo = 0.0f;
|
||||
Teleport = false;
|
||||
ArriveTime = 0;
|
||||
DepartureTime = 0;
|
||||
Spline = null;
|
||||
NextDistFromPrev = 0.0f;
|
||||
NextArriveTime = 0;
|
||||
}
|
||||
|
||||
public uint Index;
|
||||
public TaxiPathNodeRecord Node;
|
||||
public float InitialOrientation;
|
||||
public float DistSinceStop;
|
||||
public float DistUntilStop;
|
||||
public float DistFromPrev;
|
||||
public float TimeFrom;
|
||||
public float TimeTo;
|
||||
public bool Teleport;
|
||||
public uint ArriveTime;
|
||||
public uint DepartureTime;
|
||||
public Spline Spline;
|
||||
|
||||
// Data needed for next frame
|
||||
public float NextDistFromPrev;
|
||||
public uint NextArriveTime;
|
||||
|
||||
public bool IsTeleportFrame() { return Teleport; }
|
||||
public bool IsStopFrame() { return Node.Flags.HasAnyFlag(TaxiPathNodeFlags.Stop); }
|
||||
}
|
||||
|
||||
public class TransportTemplate
|
||||
{
|
||||
public TransportTemplate()
|
||||
{
|
||||
pathTime = 0;
|
||||
accelTime = 0.0f;
|
||||
accelDist = 0.0f;
|
||||
}
|
||||
|
||||
public List<uint> mapsUsed = new List<uint>();
|
||||
public bool inInstance;
|
||||
public uint pathTime;
|
||||
public List<KeyFrame> keyFrames = new List<KeyFrame>();
|
||||
public float accelTime;
|
||||
public float accelDist;
|
||||
public uint entry;
|
||||
}
|
||||
|
||||
public class TransportAnimation
|
||||
{
|
||||
TransportAnimationRecord GetAnimNode(uint time)
|
||||
{
|
||||
if (Path.Empty())
|
||||
return null;
|
||||
|
||||
foreach (var pair in Path)
|
||||
if (time >= pair.Key)
|
||||
return pair.Value;
|
||||
|
||||
return Path.First().Value;
|
||||
}
|
||||
|
||||
Quaternion GetAnimRotation(uint time)
|
||||
{
|
||||
if (Rotations.Empty())
|
||||
return new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
TransportRotationRecord rot = Rotations.First().Value;
|
||||
foreach (var pair in Rotations)
|
||||
{
|
||||
if (time >= pair.Key)
|
||||
{
|
||||
rot = pair.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Quaternion(rot.X, rot.Y, rot.Z, rot.W);
|
||||
}
|
||||
|
||||
public Dictionary<uint, TransportAnimationRecord> Path = new Dictionary<uint, TransportAnimationRecord>();
|
||||
public Dictionary<uint, TransportRotationRecord> Rotations = new Dictionary<uint, TransportRotationRecord>();
|
||||
public uint TotalTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Dynamic;
|
||||
using Game.Entities;
|
||||
|
||||
namespace Game.Maps
|
||||
{
|
||||
public class ZoneScript
|
||||
{
|
||||
public virtual uint GetCreatureEntry(ulong guidlow, CreatureData data) { return data.id; }
|
||||
public virtual uint GetGameObjectEntry(ulong spawnId, uint entry) { return entry; }
|
||||
|
||||
public virtual void OnCreatureCreate(Creature creature) { }
|
||||
public virtual void OnCreatureRemove(Creature creature) { }
|
||||
|
||||
public virtual void OnGameObjectCreate(GameObject go) { }
|
||||
public virtual void OnGameObjectRemove(GameObject go) { }
|
||||
|
||||
public virtual void OnUnitDeath(Unit unit) { }
|
||||
|
||||
//All-purpose data storage 64 bit
|
||||
public virtual ObjectGuid GetGuidData(uint DataId) { return ObjectGuid.Empty; }
|
||||
public virtual void SetGuidData(uint DataId, ObjectGuid Value) { }
|
||||
|
||||
public virtual ulong GetData64(uint dataId) { return 0; }
|
||||
public virtual void SetData64(uint dataId, ulong value) { }
|
||||
|
||||
//All-purpose data storage 32 bit
|
||||
public virtual uint GetData(uint dataId) { return 0; }
|
||||
public virtual void SetData(uint dataId, uint value) { }
|
||||
|
||||
public virtual void ProcessEvent(WorldObject obj, uint eventId) { }
|
||||
|
||||
protected EventMap _events = new EventMap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user