
-Removal of Q00344_1000YearsTheEndOfLamentation. -Fixed starting conditions for Q00144_PailakaInjuredDragon. -Fixed starting conditions for last Seven Sign quests. -Added missing MonasteryOfSilence.xml instance spawns and doors. -Removed many catacomb spawns.
411 lines
11 KiB
Java
411 lines
11 KiB
Java
/*
|
|
* Copyright (C) 2004-2015 L2J Server
|
|
*
|
|
* This file is part of L2J Server.
|
|
*
|
|
* L2J Server 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.
|
|
*
|
|
* L2J Server 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/>.
|
|
*/
|
|
package com.l2jserver.gameserver.pathfinding.cellnodes;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.ListIterator;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import com.l2jserver.Config;
|
|
import com.l2jserver.gameserver.GeoData;
|
|
import com.l2jserver.gameserver.idfactory.IdFactory;
|
|
import com.l2jserver.gameserver.model.itemcontainer.Inventory;
|
|
import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
|
|
import com.l2jserver.gameserver.pathfinding.AbstractNode;
|
|
import com.l2jserver.gameserver.pathfinding.AbstractNodeLoc;
|
|
import com.l2jserver.gameserver.pathfinding.PathFinding;
|
|
import com.l2jserver.util.StringUtil;
|
|
|
|
/**
|
|
* @author Sami, DS Credits to Diamond
|
|
*/
|
|
public class CellPathFinding extends PathFinding
|
|
{
|
|
private static final Logger _log = Logger.getLogger(CellPathFinding.class.getName());
|
|
private BufferInfo[] _allBuffers;
|
|
private int _findSuccess = 0;
|
|
private int _findFails = 0;
|
|
private int _postFilterUses = 0;
|
|
private int _postFilterPlayableUses = 0;
|
|
private int _postFilterPasses = 0;
|
|
private long _postFilterElapsed = 0;
|
|
|
|
private ArrayList<L2ItemInstance> _debugItems = null;
|
|
|
|
public static CellPathFinding getInstance()
|
|
{
|
|
return SingletonHolder._instance;
|
|
}
|
|
|
|
protected CellPathFinding()
|
|
{
|
|
try
|
|
{
|
|
String[] array = Config.PATHFIND_BUFFERS.split(";");
|
|
|
|
_allBuffers = new BufferInfo[array.length];
|
|
|
|
String buf;
|
|
String[] args;
|
|
for (int i = 0; i < array.length; i++)
|
|
{
|
|
buf = array[i];
|
|
args = buf.split("x");
|
|
if (args.length != 2)
|
|
{
|
|
throw new Exception("Invalid buffer definition: " + buf);
|
|
}
|
|
|
|
_allBuffers[i] = new BufferInfo(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_log.log(Level.WARNING, "CellPathFinding: Problem during buffer init: " + e.getMessage(), e);
|
|
throw new Error("CellPathFinding: load aborted");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean pathNodesExist(short regionoffset)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public List<AbstractNodeLoc> findPath(int x, int y, int z, int tx, int ty, int tz, int instanceId, boolean playable)
|
|
{
|
|
int gx = GeoData.getInstance().getGeoX(x);
|
|
int gy = GeoData.getInstance().getGeoY(y);
|
|
if (!GeoData.getInstance().hasGeo(x, y))
|
|
{
|
|
return null;
|
|
}
|
|
int gz = GeoData.getInstance().getHeight(x, y, z);
|
|
int gtx = GeoData.getInstance().getGeoX(tx);
|
|
int gty = GeoData.getInstance().getGeoY(ty);
|
|
if (!GeoData.getInstance().hasGeo(tx, ty))
|
|
{
|
|
return null;
|
|
}
|
|
int gtz = GeoData.getInstance().getHeight(tx, ty, tz);
|
|
CellNodeBuffer buffer = alloc(64 + (2 * Math.max(Math.abs(gx - gtx), Math.abs(gy - gty))), playable);
|
|
if (buffer == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
boolean debug = playable && Config.DEBUG_PATH;
|
|
|
|
if (debug)
|
|
{
|
|
if (_debugItems == null)
|
|
{
|
|
_debugItems = new ArrayList<>();
|
|
}
|
|
else
|
|
{
|
|
for (L2ItemInstance item : _debugItems)
|
|
{
|
|
if (item == null)
|
|
{
|
|
continue;
|
|
}
|
|
item.decayMe();
|
|
}
|
|
|
|
_debugItems.clear();
|
|
}
|
|
}
|
|
|
|
ArrayList<AbstractNodeLoc> path = null;
|
|
try
|
|
{
|
|
CellNode result = buffer.findPath(gx, gy, gz, gtx, gty, gtz);
|
|
|
|
if (debug)
|
|
{
|
|
for (CellNode n : buffer.debugPath())
|
|
{
|
|
if (n.getCost() < 0)
|
|
{
|
|
dropDebugItem(1831, (int) (-n.getCost() * 10), n.getLoc());
|
|
}
|
|
else
|
|
{
|
|
// known nodes
|
|
dropDebugItem(Inventory.ADENA_ID, (int) (n.getCost() * 10), n.getLoc());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result == null)
|
|
{
|
|
_findFails++;
|
|
return null;
|
|
}
|
|
|
|
path = constructPath(result);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_log.log(Level.WARNING, "", e);
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
buffer.free();
|
|
}
|
|
|
|
if ((path.size() < 3) || (Config.MAX_POSTFILTER_PASSES <= 0))
|
|
{
|
|
_findSuccess++;
|
|
return path;
|
|
}
|
|
|
|
long timeStamp = System.currentTimeMillis();
|
|
_postFilterUses++;
|
|
if (playable)
|
|
{
|
|
_postFilterPlayableUses++;
|
|
}
|
|
|
|
int currentX, currentY, currentZ;
|
|
ListIterator<AbstractNodeLoc> middlePoint, endPoint;
|
|
boolean remove;
|
|
int pass = 0;
|
|
do
|
|
{
|
|
pass++;
|
|
_postFilterPasses++;
|
|
|
|
remove = false;
|
|
middlePoint = path.listIterator();
|
|
endPoint = path.listIterator(1);
|
|
currentX = x;
|
|
currentY = y;
|
|
currentZ = z;
|
|
|
|
while (endPoint.hasNext())
|
|
{
|
|
AbstractNodeLoc locEnd = endPoint.next();
|
|
AbstractNodeLoc locMiddle = middlePoint.next();
|
|
if (GeoData.getInstance().canMove(currentX, currentY, currentZ, locEnd.getX(), locEnd.getY(), locEnd.getZ(), instanceId))
|
|
{
|
|
middlePoint.remove();
|
|
remove = true;
|
|
if (debug)
|
|
{
|
|
dropDebugItem(735, 1, locMiddle);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentX = locMiddle.getX();
|
|
currentY = locMiddle.getY();
|
|
currentZ = locMiddle.getZ();
|
|
}
|
|
}
|
|
}
|
|
// only one postfilter pass for AI
|
|
while (playable && remove && (path.size() > 2) && (pass < Config.MAX_POSTFILTER_PASSES));
|
|
|
|
if (debug)
|
|
{
|
|
middlePoint = path.listIterator();
|
|
while (middlePoint.hasNext())
|
|
{
|
|
dropDebugItem(65, 1, middlePoint.next());
|
|
}
|
|
}
|
|
|
|
_findSuccess++;
|
|
_postFilterElapsed += System.currentTimeMillis() - timeStamp;
|
|
return path;
|
|
}
|
|
|
|
private ArrayList<AbstractNodeLoc> constructPath(AbstractNode node)
|
|
{
|
|
ArrayList<AbstractNodeLoc> path = new ArrayList<>();
|
|
int previousDirectionX = Integer.MIN_VALUE;
|
|
int previousDirectionY = Integer.MIN_VALUE;
|
|
int directionX, directionY;
|
|
|
|
while (node.getParent() != null)
|
|
{
|
|
if (!Config.ADVANCED_DIAGONAL_STRATEGY && (node.getParent().getParent() != null))
|
|
{
|
|
int tmpX = node.getLoc().getNodeX() - node.getParent().getParent().getLoc().getNodeX();
|
|
int tmpY = node.getLoc().getNodeY() - node.getParent().getParent().getLoc().getNodeY();
|
|
if (Math.abs(tmpX) == Math.abs(tmpY))
|
|
{
|
|
directionX = tmpX;
|
|
directionY = tmpY;
|
|
}
|
|
else
|
|
{
|
|
directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX();
|
|
directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
directionX = node.getLoc().getNodeX() - node.getParent().getLoc().getNodeX();
|
|
directionY = node.getLoc().getNodeY() - node.getParent().getLoc().getNodeY();
|
|
}
|
|
|
|
// only add a new route point if moving direction changes
|
|
if ((directionX != previousDirectionX) || (directionY != previousDirectionY))
|
|
{
|
|
previousDirectionX = directionX;
|
|
previousDirectionY = directionY;
|
|
|
|
path.add(node.getLoc());
|
|
node.setLoc(null);
|
|
}
|
|
|
|
node = node.getParent();
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
private final CellNodeBuffer alloc(int size, boolean playable)
|
|
{
|
|
CellNodeBuffer current = null;
|
|
for (BufferInfo i : _allBuffers)
|
|
{
|
|
if (i.mapSize >= size)
|
|
{
|
|
for (CellNodeBuffer buf : i.bufs)
|
|
{
|
|
if (buf.lock())
|
|
{
|
|
i.uses++;
|
|
if (playable)
|
|
{
|
|
i.playableUses++;
|
|
}
|
|
i.elapsed += buf.getElapsedTime();
|
|
current = buf;
|
|
break;
|
|
}
|
|
}
|
|
if (current != null)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// not found, allocate temporary buffer
|
|
current = new CellNodeBuffer(i.mapSize);
|
|
current.lock();
|
|
if (i.bufs.size() < i.count)
|
|
{
|
|
i.bufs.add(current);
|
|
i.uses++;
|
|
if (playable)
|
|
{
|
|
i.playableUses++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
i.overflows++;
|
|
if (playable)
|
|
{
|
|
i.playableOverflows++;
|
|
// System.err.println("Overflow, size requested: " + size + " playable:"+playable);
|
|
}
|
|
}
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
private final void dropDebugItem(int itemId, int num, AbstractNodeLoc loc)
|
|
{
|
|
final L2ItemInstance item = new L2ItemInstance(IdFactory.getInstance().getNextId(), itemId);
|
|
item.setCount(num);
|
|
item.spawnMe(loc.getX(), loc.getY(), loc.getZ());
|
|
_debugItems.add(item);
|
|
}
|
|
|
|
private static final class BufferInfo
|
|
{
|
|
final int mapSize;
|
|
final int count;
|
|
ArrayList<CellNodeBuffer> bufs;
|
|
int uses = 0;
|
|
int playableUses = 0;
|
|
int overflows = 0;
|
|
int playableOverflows = 0;
|
|
long elapsed = 0;
|
|
|
|
public BufferInfo(int size, int cnt)
|
|
{
|
|
mapSize = size;
|
|
count = cnt;
|
|
bufs = new ArrayList<>(count);
|
|
}
|
|
|
|
@Override
|
|
public String toString()
|
|
{
|
|
final StringBuilder stat = new StringBuilder(100);
|
|
StringUtil.append(stat, String.valueOf(mapSize), "x", String.valueOf(mapSize), " num:", String.valueOf(bufs.size()), "/", String.valueOf(count), " uses:", String.valueOf(uses), "/", String.valueOf(playableUses));
|
|
if (uses > 0)
|
|
{
|
|
StringUtil.append(stat, " total/avg(ms):", String.valueOf(elapsed), "/", String.format("%1.2f", (double) elapsed / uses));
|
|
}
|
|
|
|
StringUtil.append(stat, " ovf:", String.valueOf(overflows), "/", String.valueOf(playableOverflows));
|
|
|
|
return stat.toString();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String[] getStat()
|
|
{
|
|
final String[] result = new String[_allBuffers.length + 1];
|
|
for (int i = 0; i < _allBuffers.length; i++)
|
|
{
|
|
result[i] = _allBuffers[i].toString();
|
|
}
|
|
|
|
final StringBuilder stat = new StringBuilder(100);
|
|
StringUtil.append(stat, "LOS postfilter uses:", String.valueOf(_postFilterUses), "/", String.valueOf(_postFilterPlayableUses));
|
|
if (_postFilterUses > 0)
|
|
{
|
|
StringUtil.append(stat, " total/avg(ms):", String.valueOf(_postFilterElapsed), "/", String.format("%1.2f", (double) _postFilterElapsed / _postFilterUses), " passes total/avg:", String.valueOf(_postFilterPasses), "/", String.format("%1.1f", (double) _postFilterPasses / _postFilterUses), Config.EOL);
|
|
}
|
|
StringUtil.append(stat, "Pathfind success/fail:", String.valueOf(_findSuccess), "/", String.valueOf(_findFails));
|
|
result[result.length - 1] = stat.toString();
|
|
|
|
return result;
|
|
}
|
|
|
|
private static class SingletonHolder
|
|
{
|
|
protected static final CellPathFinding _instance = new CellPathFinding();
|
|
}
|
|
}
|