/*
* 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 .
*/
package com.l2jserver.gameserver.instancemanager;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jserver.gameserver.model.L2Object;
import com.l2jserver.gameserver.model.L2World;
import com.l2jserver.gameserver.model.L2WorldRegion;
import com.l2jserver.gameserver.model.actor.L2Character;
import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
import com.l2jserver.gameserver.model.zone.AbstractZoneSettings;
import com.l2jserver.gameserver.model.zone.L2ZoneForm;
import com.l2jserver.gameserver.model.zone.L2ZoneRespawn;
import com.l2jserver.gameserver.model.zone.L2ZoneType;
import com.l2jserver.gameserver.model.zone.form.ZoneCuboid;
import com.l2jserver.gameserver.model.zone.form.ZoneCylinder;
import com.l2jserver.gameserver.model.zone.form.ZoneNPoly;
import com.l2jserver.gameserver.model.zone.type.L2ArenaZone;
import com.l2jserver.gameserver.model.zone.type.L2OlympiadStadiumZone;
import com.l2jserver.gameserver.model.zone.type.L2RespawnZone;
import com.l2jserver.gameserver.model.zone.type.NpcSpawnTerritory;
import com.l2jserver.util.data.xml.IXmlReader;
/**
* This class manages the zones
* @author durgus
*/
public final class ZoneManager implements IXmlReader
{
private static final Map _settings = new HashMap<>();
private final Map, Map> _classZones = new HashMap<>();
private final Map _spawnTerritories = new HashMap<>();
private int _lastDynamicId = 300000;
private List _debugItems;
/**
* Instantiates a new zone manager.
*/
protected ZoneManager()
{
load();
}
/**
* Reload.
*/
public void reload()
{
// Get the world regions
int count = 0;
final L2WorldRegion[][] worldRegions = L2World.getInstance().getWorldRegions();
// Backup old zone settings
for (Map map : _classZones.values())
{
for (L2ZoneType zone : map.values())
{
if (zone.getSettings() != null)
{
_settings.put(zone.getName(), zone.getSettings());
}
}
}
// Clear zones
for (L2WorldRegion[] worldRegion : worldRegions)
{
for (L2WorldRegion element : worldRegion)
{
element.getZones().clear();
count++;
}
}
GrandBossManager.getInstance().getZones().clear();
LOGGER.info("{}: Removed zones in " + count + " regions.", getClass().getSimpleName());
// Load the zones
load();
// Re-validate all characters in zones
for (L2Object obj : L2World.getInstance().getVisibleObjects())
{
if (obj instanceof L2Character)
{
((L2Character) obj).revalidateZone(true);
}
}
_settings.clear();
}
@Override
public void parseDocument(Document doc, File f)
{
// Get the world regions
final L2WorldRegion[][] worldRegions = L2World.getInstance().getWorldRegions();
NamedNodeMap attrs;
Node attribute;
String zoneName;
int[][] coords;
int zoneId, minZ, maxZ;
String zoneType, zoneShape;
final List rs = new ArrayList<>();
for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling())
{
if ("list".equalsIgnoreCase(n.getNodeName()))
{
attrs = n.getAttributes();
attribute = attrs.getNamedItem("enabled");
if ((attribute != null) && !Boolean.parseBoolean(attribute.getNodeValue()))
{
continue;
}
for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("zone".equalsIgnoreCase(d.getNodeName()))
{
attrs = d.getAttributes();
attribute = attrs.getNamedItem("type");
if (attribute != null)
{
zoneType = attribute.getNodeValue();
}
else
{
LOGGER.warn("ZoneData: Missing type for zone in file {}", f.getName());
continue;
}
attribute = attrs.getNamedItem("id");
if (attribute != null)
{
zoneId = Integer.parseInt(attribute.getNodeValue());
}
else
{
zoneId = zoneType.equalsIgnoreCase("NpcSpawnTerritory") ? 0 : _lastDynamicId++;
}
attribute = attrs.getNamedItem("name");
if (attribute != null)
{
zoneName = attribute.getNodeValue();
}
else
{
zoneName = null;
}
// Check zone name for NpcSpawnTerritory. Must exist and to be unique
if (zoneType.equalsIgnoreCase("NpcSpawnTerritory"))
{
if (zoneName == null)
{
LOGGER.warn("ZoneData: Missing name for NpcSpawnTerritory in file: {}, skipping zone!", f.getName());
continue;
}
else if (_spawnTerritories.containsKey(zoneName))
{
LOGGER.warn("ZoneData: Name {} already used for another zone, check file {}, skipping zone!", zoneName, f.getName());
continue;
}
}
minZ = parseInteger(attrs, "minZ");
maxZ = parseInteger(attrs, "maxZ");
zoneType = parseString(attrs, "type");
zoneShape = parseString(attrs, "shape");
// Get the zone shape from xml
L2ZoneForm zoneForm = null;
try
{
for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
{
if ("node".equalsIgnoreCase(cd.getNodeName()))
{
attrs = cd.getAttributes();
int[] point = new int[2];
point[0] = parseInteger(attrs, "X");
point[1] = parseInteger(attrs, "Y");
rs.add(point);
}
}
coords = rs.toArray(new int[rs.size()][2]);
rs.clear();
if ((coords == null) || (coords.length == 0))
{
LOGGER.warn("{}: ZoneData: missing data for zone: {} XML file {}!", getClass().getSimpleName(), zoneId, f.getName());
continue;
}
// Create this zone. Parsing for cuboids is a bit different than for other polygons cuboids need exactly 2 points to be defined.
// Other polygons need at least 3 (one per vertex)
if (zoneShape.equalsIgnoreCase("Cuboid"))
{
if (coords.length == 2)
{
zoneForm = new ZoneCuboid(coords[0][0], coords[1][0], coords[0][1], coords[1][1], minZ, maxZ);
}
else
{
LOGGER.warn("{}: ZoneData: Missing cuboid vertex in sql data for zone: {} in file {}!", getClass().getSimpleName(), zoneId, f.getName());
continue;
}
}
else if (zoneShape.equalsIgnoreCase("NPoly"))
{
// nPoly needs to have at least 3 vertices
if (coords.length > 2)
{
final int[] aX = new int[coords.length];
final int[] aY = new int[coords.length];
for (int i = 0; i < coords.length; i++)
{
aX[i] = coords[i][0];
aY[i] = coords[i][1];
}
zoneForm = new ZoneNPoly(aX, aY, minZ, maxZ);
}
else
{
LOGGER.warn("{}: ZoneData: Bad data for zone: {} in file {}!", getClass().getSimpleName(), zoneId, f.getName());
continue;
}
}
else if (zoneShape.equalsIgnoreCase("Cylinder"))
{
// A Cylinder zone requires a center point
// at x,y and a radius
attrs = d.getAttributes();
final int zoneRad = Integer.parseInt(attrs.getNamedItem("rad").getNodeValue());
if ((coords.length == 1) && (zoneRad > 0))
{
zoneForm = new ZoneCylinder(coords[0][0], coords[0][1], minZ, maxZ, zoneRad);
}
else
{
LOGGER.warn("{}: ZoneData: Bad data for zone: {} in file {}!", getClass().getSimpleName(), zoneId, f.getName());
continue;
}
}
else
{
LOGGER.warn("{}: ZoneData: Unknown shape: {} for zone {} in file {}", getClass().getSimpleName(), zoneShape, zoneId, f.getName());
continue;
}
}
catch (Exception e)
{
LOGGER.warn("{}: ZoneData: Failed to load zone {} coordinates!", getClass().getSimpleName(), zoneId, e);
}
// No further parameters needed, if NpcSpawnTerritory is loading
if (zoneType.equalsIgnoreCase("NpcSpawnTerritory"))
{
_spawnTerritories.put(zoneName, new NpcSpawnTerritory(zoneName, zoneForm));
continue;
}
// Create the zone
Class> newZone = null;
Constructor> zoneConstructor = null;
L2ZoneType temp;
try
{
newZone = Class.forName("com.l2jserver.gameserver.model.zone.type.L2" + zoneType);
zoneConstructor = newZone.getConstructor(int.class);
temp = (L2ZoneType) zoneConstructor.newInstance(zoneId);
temp.setZone(zoneForm);
}
catch (Exception e)
{
LOGGER.warn("{}: ZoneData: No such zone type: {} in file {}!", getClass().getSimpleName(), zoneType, f.getName());
continue;
}
// Check for additional parameters
for (Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
{
if ("stat".equalsIgnoreCase(cd.getNodeName()))
{
attrs = cd.getAttributes();
String name = attrs.getNamedItem("name").getNodeValue();
String val = attrs.getNamedItem("val").getNodeValue();
temp.setParameter(name, val);
}
else if ("spawn".equalsIgnoreCase(cd.getNodeName()) && (temp instanceof L2ZoneRespawn))
{
attrs = cd.getAttributes();
int spawnX = Integer.parseInt(attrs.getNamedItem("X").getNodeValue());
int spawnY = Integer.parseInt(attrs.getNamedItem("Y").getNodeValue());
int spawnZ = Integer.parseInt(attrs.getNamedItem("Z").getNodeValue());
Node val = attrs.getNamedItem("type");
((L2ZoneRespawn) temp).parseLoc(spawnX, spawnY, spawnZ, val == null ? null : val.getNodeValue());
}
else if ("race".equalsIgnoreCase(cd.getNodeName()) && (temp instanceof L2RespawnZone))
{
attrs = cd.getAttributes();
String race = attrs.getNamedItem("name").getNodeValue();
String point = attrs.getNamedItem("point").getNodeValue();
((L2RespawnZone) temp).addRaceRespawnPoint(race, point);
}
}
if (checkId(zoneId))
{
LOGGER.debug("{}: Caution: Zone ({}) from file {} overrides previous definition.", getClass().getSimpleName(), zoneId, f.getName());
}
if ((zoneName != null) && !zoneName.isEmpty())
{
temp.setName(zoneName);
}
addZone(zoneId, temp);
// Register the zone into any world region it intersects with...
// currently 11136 test for each zone :>
int ax, ay, bx, by;
for (int x = 0; x < worldRegions.length; x++)
{
for (int y = 0; y < worldRegions[x].length; y++)
{
ax = (x - L2World.OFFSET_X) << L2World.SHIFT_BY;
bx = ((x + 1) - L2World.OFFSET_X) << L2World.SHIFT_BY;
ay = (y - L2World.OFFSET_Y) << L2World.SHIFT_BY;
by = ((y + 1) - L2World.OFFSET_Y) << L2World.SHIFT_BY;
if (temp.getZone().intersectsRectangle(ax, bx, ay, by))
{
worldRegions[x][y].addZone(temp);
}
}
}
}
}
}
}
}
@Override
public final void load()
{
_classZones.clear();
_spawnTerritories.clear();
parseDatapackDirectory("data/zones", false);
parseDatapackDirectory("data/zones/npcSpawnTerritories", false);
LOGGER.info("{}: Loaded {} zone classes and {} zones.", getClass().getSimpleName(), _classZones.size(), getSize());
LOGGER.info("{}: Loaded {} NPC spawn territoriers.", getClass().getSimpleName(), _spawnTerritories.size());
}
/**
* Gets the size.
* @return the size
*/
public int getSize()
{
int i = 0;
for (Map map : _classZones.values())
{
i += map.size();
}
return i;
}
/**
* Check id.
* @param id the id
* @return true, if successful
*/
public boolean checkId(int id)
{
for (Map map : _classZones.values())
{
if (map.containsKey(id))
{
return true;
}
}
return false;
}
/**
* Add new zone.
* @param the generic type
* @param id the id
* @param zone the zone
*/
@SuppressWarnings("unchecked")
public void addZone(Integer id, T zone)
{
Map map = (Map) _classZones.get(zone.getClass());
if (map == null)
{
map = new HashMap<>();
map.put(id, zone);
_classZones.put(zone.getClass(), map);
}
else
{
map.put(id, zone);
}
}
/**
* Returns all zones registered with the ZoneManager. To minimize iteration processing retrieve zones from L2WorldRegion for a specific location instead.
* @return zones
* @see #getAllZones(Class)
*/
@Deprecated
public Collection getAllZones()
{
final List zones = new ArrayList<>();
for (Map map : _classZones.values())
{
zones.addAll(map.values());
}
return zones;
}
/**
* Return all zones by class type.
* @param the generic type
* @param zoneType Zone class
* @return Collection of zones
*/
@SuppressWarnings("unchecked")
public Collection getAllZones(Class zoneType)
{
return (Collection) _classZones.get(zoneType).values();
}
/**
* Get zone by ID.
* @param id the id
* @return the zone by id
* @see #getZoneById(int, Class)
*/
public L2ZoneType getZoneById(int id)
{
for (Map map : _classZones.values())
{
if (map.containsKey(id))
{
return map.get(id);
}
}
return null;
}
/**
* Get zone by ID and zone class.
* @param the generic type
* @param id the id
* @param zoneType the zone type
* @return zone
*/
@SuppressWarnings("unchecked")
public T getZoneById(int id, Class zoneType)
{
return (T) _classZones.get(zoneType).get(id);
}
/**
* Returns all zones from where the object is located.
* @param object the object
* @return zones
*/
public List getZones(L2Object object)
{
return getZones(object.getX(), object.getY(), object.getZ());
}
/**
* Gets the zone.
* @param the generic type
* @param object the object
* @param type the type
* @return zone from where the object is located by type
*/
public T getZone(L2Object object, Class type)
{
if (object == null)
{
return null;
}
return getZone(object.getX(), object.getY(), object.getZ(), type);
}
/**
* Returns all zones from given coordinates (plane).
* @param x the x
* @param y the y
* @return zones
*/
public List getZones(int x, int y)
{
final L2WorldRegion region = L2World.getInstance().getRegion(x, y);
final List temp = new ArrayList<>();
for (L2ZoneType zone : region.getZones())
{
if (zone.isInsideZone(x, y))
{
temp.add(zone);
}
}
return temp;
}
/**
* Returns all zones from given coordinates.
* @param x the x
* @param y the y
* @param z the z
* @return zones
*/
public List getZones(int x, int y, int z)
{
final L2WorldRegion region = L2World.getInstance().getRegion(x, y);
final List temp = new ArrayList<>();
for (L2ZoneType zone : region.getZones())
{
if (zone.isInsideZone(x, y, z))
{
temp.add(zone);
}
}
return temp;
}
/**
* Gets the zone.
* @param the generic type
* @param x the x
* @param y the y
* @param z the z
* @param type the type
* @return zone from given coordinates
*/
@SuppressWarnings("unchecked")
public T getZone(int x, int y, int z, Class type)
{
final L2WorldRegion region = L2World.getInstance().getRegion(x, y);
for (L2ZoneType zone : region.getZones())
{
if (zone.isInsideZone(x, y, z) && type.isInstance(zone))
{
return (T) zone;
}
}
return null;
}
/**
* Get spawm territory by name
* @param name name of territory to search
* @return link to zone form
*/
public NpcSpawnTerritory getSpawnTerritory(String name)
{
return _spawnTerritories.containsKey(name) ? _spawnTerritories.get(name) : null;
}
/**
* Returns all spawm territories from where the object is located
* @param object
* @return zones
*/
public List getSpawnTerritories(L2Object object)
{
List temp = new ArrayList<>();
for (NpcSpawnTerritory territory : _spawnTerritories.values())
{
if (territory.isInsideZone(object.getX(), object.getY(), object.getZ()))
{
temp.add(territory);
}
}
return temp;
}
/**
* Gets the arena.
* @param character the character
* @return the arena
*/
public final L2ArenaZone getArena(L2Character character)
{
if (character == null)
{
return null;
}
for (L2ZoneType temp : ZoneManager.getInstance().getZones(character.getX(), character.getY(), character.getZ()))
{
if ((temp instanceof L2ArenaZone) && temp.isCharacterInZone(character))
{
return ((L2ArenaZone) temp);
}
}
return null;
}
/**
* Gets the olympiad stadium.
* @param character the character
* @return the olympiad stadium
*/
public final L2OlympiadStadiumZone getOlympiadStadium(L2Character character)
{
if (character == null)
{
return null;
}
for (L2ZoneType temp : ZoneManager.getInstance().getZones(character.getX(), character.getY(), character.getZ()))
{
if ((temp instanceof L2OlympiadStadiumZone) && temp.isCharacterInZone(character))
{
return ((L2OlympiadStadiumZone) temp);
}
}
return null;
}
/**
* For testing purposes only.
* @param the generic type
* @param obj the obj
* @param type the type
* @return the closest zone
*/
@SuppressWarnings("unchecked")
public T getClosestZone(L2Object obj, Class type)
{
T zone = getZone(obj, type);
if (zone == null)
{
double closestdis = Double.MAX_VALUE;
for (T temp : (Collection) _classZones.get(type).values())
{
double distance = temp.getDistanceToZone(obj);
if (distance < closestdis)
{
closestdis = distance;
zone = temp;
}
}
}
return zone;
}
/**
* General storage for debug items used for visualizing zones.
* @return list of items
*/
public List getDebugItems()
{
if (_debugItems == null)
{
_debugItems = new ArrayList<>();
}
return _debugItems;
}
/**
* Remove all debug items from l2world.
*/
public void clearDebugItems()
{
if (_debugItems != null)
{
final Iterator it = _debugItems.iterator();
while (it.hasNext())
{
final L2ItemInstance item = it.next();
if (item != null)
{
item.decayMe();
}
it.remove();
}
}
}
/**
* Gets the settings.
* @param name the name
* @return the settings
*/
public static AbstractZoneSettings getSettings(String name)
{
return _settings.get(name);
}
/**
* Gets the single instance of ZoneManager.
* @return single instance of ZoneManager
*/
public static final ZoneManager getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final ZoneManager _instance = new ZoneManager();
}
}