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:
Fabian
2017-10-26 17:23:44 +02:00
parent 227702e19c
commit a3dc7b3f48
844 changed files with 26064 additions and 1824 deletions
+271
View File
@@ -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;
}
}
+319
View File
@@ -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;
}
}
+477
View File
@@ -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
}
}
+286
View File
@@ -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; }
}
}
+712
View File
@@ -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;
}
}
+307
View File
@@ -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;
}
}
+581
View File
@@ -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
+428
View File
@@ -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;
}
}
+106
View File
@@ -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();
}
}
}
+241
View File
@@ -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();
}
}
}
}
+603
View File
@@ -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;
}
}
+51
View File
@@ -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();
}
}