/*
* Copyright (C) 2004-2013 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.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jserver.Config;
import com.l2jserver.gameserver.datatables.AdminTable;
import com.l2jserver.gameserver.datatables.CharNameTable;
import com.l2jserver.gameserver.model.actor.L2Playable;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.actor.instance.L2PetInstance;
import com.l2jserver.gameserver.model.interfaces.IL2Procedure;
import com.l2jserver.gameserver.util.Point3D;
import com.l2jserver.util.StringUtil;
public final class L2World
{
private static final Logger _log = Logger.getLogger(L2World.class.getName());
/** Gracia border Flying objects not allowed to the east of it. */
public static final int GRACIA_MAX_X = -166168;
public static final int GRACIA_MAX_Z = 6105;
public static final int GRACIA_MIN_Z = -895;
/** Biteshift, defines number of regions note, shifting by 15 will result in regions corresponding to map tiles shifting by 12 divides one tile to 8x8 regions. */
public static final int SHIFT_BY = 12;
private static final int TILE_SIZE = 32768;
/** Map dimensions */
public static final int MAP_MIN_X = (Config.WORLD_X_MIN - 20) * TILE_SIZE;
public static final int MAP_MAX_X = (Config.WORLD_X_MAX - 19) * TILE_SIZE;
public static final int MAP_MIN_Y = (Config.WORLD_Y_MIN - 18) * TILE_SIZE;
public static final int MAP_MAX_Y = (Config.WORLD_Y_MAX - 17) * TILE_SIZE;
/** calculated offset used so top left region is 0,0 */
public static final int OFFSET_X = Math.abs(MAP_MIN_X >> SHIFT_BY);
public static final int OFFSET_Y = Math.abs(MAP_MIN_Y >> SHIFT_BY);
/** number of regions */
private static final int REGIONS_X = (MAP_MAX_X >> SHIFT_BY) + OFFSET_X;
private static final int REGIONS_Y = (MAP_MAX_Y >> SHIFT_BY) + OFFSET_Y;
/** Map containing all the players in game. */
private final Map _allPlayers = new ConcurrentHashMap<>();
/** Map containing all visible objects. */
private final Map _allObjects = new ConcurrentHashMap<>();
/** Map used for debug. */
private final Map _allObjectsDebug = new ConcurrentHashMap<>();
/** Map with the pets instances and their owner ID. */
private final Map _petsInstance = new ConcurrentHashMap<>();
private L2WorldRegion[][] _worldRegions;
/** Constructor of L2World. */
protected L2World()
{
initRegions();
}
/**
* Adds an object to the world.
* Example of use:
*
* - Withdraw an item from the warehouse, create an item
* - Spawn a L2Character (PC, NPC, Pet)
*
* @param object
*/
public void storeObject(L2Object object)
{
if (_allObjects.containsKey(object.getObjectId()))
{
_log.log(Level.WARNING, getClass().getSimpleName() + ": Current object: " + object + " already exist in OID map!");
_log.log(Level.WARNING, StringUtil.getTraceString(Thread.currentThread().getStackTrace()));
_log.log(Level.WARNING, getClass().getSimpleName() + ": Previous object: " + _allObjects.get(object.getObjectId()) + " already exist in OID map!");
_log.log(Level.WARNING, _allObjectsDebug.get(object.getObjectId()));
_log.log(Level.WARNING, "---------------------- End ---------------------");
return;
}
_allObjects.put(object.getObjectId(), object);
_allObjectsDebug.put(object.getObjectId(), StringUtil.getTraceString(Thread.currentThread().getStackTrace()));
}
/**
* Removes an object from the world.
* Example of use:
*
* - Delete item from inventory, transfer Item from inventory to warehouse
* - Crystallize item
* - Remove NPC/PC/Pet from the world
*
* @param object the object to remove
*/
public void removeObject(L2Object object)
{
_allObjects.remove(object.getObjectId());
_allObjectsDebug.remove(object.getObjectId());
}
public void removeObjects(List list)
{
for (L2Object o : list)
{
if (o != null)
{
_allObjects.remove(o.getObjectId());
_allObjectsDebug.remove(o.getObjectId());
}
}
}
public void removeObjects(L2Object[] objects)
{
for (L2Object o : objects)
{
_allObjects.remove(o.getObjectId());
_allObjectsDebug.remove(o.getObjectId());
}
}
/**
* Example of use:
*
* - Client packets : Action, AttackRequest, RequestJoinParty, RequestJoinPledge...
*
* @param oID Identifier of the L2Object
* @return the L2Object object that belongs to an ID or null if no object found.
*/
public L2Object findObject(int oID)
{
return _allObjects.get(oID);
}
public Collection getVisibleObjects()
{
return _allObjects.values();
}
/**
* Get the count of all visible objects in world.
* @return count off all L2World objects
*/
public int getVisibleObjectsCount()
{
return _allObjects.size();
}
public List getAllGMs()
{
return AdminTable.getInstance().getAllGms(true);
}
public Collection getPlayers()
{
return _allPlayers.values();
}
public boolean forEachPlayer(IL2Procedure procedure)
{
for (L2PcInstance player : _allPlayers.values())
{
if (!procedure.execute(player))
{
return false;
}
}
return true;
}
/**
* Return how many players are online.
* @return number of online players.
*/
public int getAllPlayersCount()
{
return _allPlayers.size();
}
/**
* If you have access to player objectId use {@link #getPlayer(int playerObjId)}
* @param name Name of the player to get Instance
* @return the player instance corresponding to the given name.
*/
public L2PcInstance getPlayer(String name)
{
return getPlayer(CharNameTable.getInstance().getIdByName(name));
}
/**
* @param playerObjId Object ID of the player to get Instance
* @return the player instance corresponding to the given object ID.
*/
public L2PcInstance getPlayer(int playerObjId)
{
return _allPlayers.get(playerObjId);
}
/**
* @param ownerId ID of the owner
* @return the pet instance from the given ownerId.
*/
public L2PetInstance getPet(int ownerId)
{
return _petsInstance.get(ownerId);
}
/**
* Add the given pet instance from the given ownerId.
* @param ownerId ID of the owner
* @param pet L2PetInstance of the pet
* @return
*/
public L2PetInstance addPet(int ownerId, L2PetInstance pet)
{
return _petsInstance.put(ownerId, pet);
}
/**
* Remove the given pet instance.
* @param ownerId ID of the owner
*/
public void removePet(int ownerId)
{
_petsInstance.remove(ownerId);
}
/**
* Remove the given pet instance.
* @param pet the pet to remove
*/
public void removePet(L2PetInstance pet)
{
_petsInstance.remove(pet.getOwner().getObjectId());
}
/**
* Add a L2Object in the world. Concept : L2Object (including L2PcInstance) are identified in _visibleObjects of his current L2WorldRegion and in _knownObjects of other surrounding L2Characters
* L2PcInstance are identified in _allPlayers of L2World, in _allPlayers of his current L2WorldRegion and in _knownPlayer of other surrounding L2Characters Actions : Add the L2Object object in _allPlayers* of L2World Add the L2Object object in
* _gmList** of GmListTable Add object in _knownObjects and _knownPlayer* of all surrounding L2WorldRegion L2Characters
* If object is a L2Character, add all surrounding L2Object in its _knownObjects and all surrounding L2PcInstance in its _knownPlayer
* * only if object is a L2PcInstance
* ** only if object is a GM L2PcInstance Caution : This method DOESN'T ADD the object in _visibleObjects and _allPlayers* of L2WorldRegion (need synchronisation)
* Caution : This method DOESN'T ADD the object to _allObjects and _allPlayers* of L2World (need synchronisation) Example of use : Drop an Item Spawn a L2Character Apply Death Penalty of a L2PcInstance
* @param object L2object to add in the world
* @param newRegion L2WorldRegion in wich the object will be add (not used)
*/
public void addVisibleObject(L2Object object, L2WorldRegion newRegion)
{
// TODO: this code should be obsoleted by protection in putObject func...
if (object.isPlayer())
{
L2PcInstance player = object.getActingPlayer();
if (!player.isTeleporting())
{
final L2PcInstance old = getPlayer(player.getObjectId());
if (old != null)
{
_log.warning("Duplicate character!? Closing both characters (" + player.getName() + ")");
player.logout();
old.logout();
return;
}
addPlayerToWorld(player);
}
}
if (!newRegion.isActive())
{
return;
}
// Get all visible objects contained in the _visibleObjects of L2WorldRegions
// in a circular area of 2000 units
List visibles = getVisibleObjects(object, 2000);
if (Config.DEBUG)
{
_log.finest("objects in range:" + visibles.size());
}
// tell the player about the surroundings
// Go through the visible objects contained in the circular area
for (L2Object visible : visibles)
{
if (visible == null)
{
continue;
}
// Add the object in L2ObjectHashSet(L2Object) _knownObjects of the visible L2Character according to conditions :
// - L2Character is visible
// - object is not already known
// - object is in the watch distance
// If L2Object is a L2PcInstance, add L2Object in L2ObjectHashSet(L2PcInstance) _knownPlayer of the visible L2Character
visible.getKnownList().addKnownObject(object);
// Add the visible L2Object in L2ObjectHashSet(L2Object) _knownObjects of the object according to conditions
// If visible L2Object is a L2PcInstance, add visible L2Object in L2ObjectHashSet(L2PcInstance) _knownPlayer of the object
object.getKnownList().addKnownObject(visible);
}
}
/**
* Adds the player to the world.
* @param player the player to add
*/
public void addPlayerToWorld(L2PcInstance player)
{
_allPlayers.put(player.getObjectId(), player);
}
/**
* Remove the player from the world.
* @param player the player to remove
*/
public void removeFromAllPlayers(L2PcInstance player)
{
_allPlayers.remove(player.getObjectId());
}
/**
* Remove a L2Object from the world. Concept : L2Object (including L2PcInstance) are identified in _visibleObjects of his current L2WorldRegion and in _knownObjects of other surrounding L2Characters
* L2PcInstance are identified in _allPlayers of L2World, in _allPlayers of his current L2WorldRegion and in _knownPlayer of other surrounding L2Characters Actions : Remove the L2Object object from _allPlayers* of L2World Remove the L2Object
* object from _visibleObjects and _allPlayers* of L2WorldRegion Remove the L2Object object from _gmList** of GmListTable Remove object from _knownObjects and _knownPlayer* of all surrounding L2WorldRegion L2Characters
* If object is a L2Character, remove all L2Object from its _knownObjects and all L2PcInstance from its _knownPlayer Caution : This method DOESN'T REMOVE the object from _allObjects of L2World * only if object is a L2PcInstance
* ** only if object is a GM L2PcInstance Example of use : Pickup an Item Decay a L2Character
* @param object L2object to remove from the world
* @param oldRegion L2WorldRegion in which the object was before removing
*/
public void removeVisibleObject(L2Object object, L2WorldRegion oldRegion)
{
if (object == null)
{
return;
}
if (oldRegion != null)
{
// Remove the object from the L2ObjectHashSet(L2Object) _visibleObjects of L2WorldRegion
// If object is a L2PcInstance, remove it from the L2ObjectHashSet(L2PcInstance) _allPlayers of this L2WorldRegion
oldRegion.removeVisibleObject(object);
// Go through all surrounding L2WorldRegion L2Characters
for (L2WorldRegion reg : oldRegion.getSurroundingRegions())
{
final Collection vObj = reg.getVisibleObjects().values();
for (L2Object obj : vObj)
{
if (obj != null)
{
obj.getKnownList().removeKnownObject(object);
}
}
}
// If object is a L2Character :
// Remove all L2Object from L2ObjectHashSet(L2Object) containing all L2Object detected by the L2Character
// Remove all L2PcInstance from L2ObjectHashSet(L2PcInstance) containing all player ingame detected by the L2Character
object.getKnownList().removeAllKnownObjects();
// If selected L2Object is a L2PcIntance, remove it from L2ObjectHashSet(L2PcInstance) _allPlayers of L2World
if (object.isPlayer())
{
final L2PcInstance player = object.getActingPlayer();
if (!player.isTeleporting())
{
removeFromAllPlayers(player);
}
}
}
}
/**
* Return all visible objects of the L2WorldRegion object's and of its surrounding L2WorldRegion. Concept : All visible object are identified in _visibleObjects of their current L2WorldRegion
* All surrounding L2WorldRegion are identified in _surroundingRegions of the selected L2WorldRegion in order to scan a large area around a L2Object Example of use : Find Close Objects for L2Character
* @param object L2object that determine the current L2WorldRegion
* @return
*/
public List getVisibleObjects(L2Object object)
{
L2WorldRegion reg = object.getWorldRegion();
if (reg == null)
{
return null;
}
// Create an FastList in order to contain all visible L2Object
List result = new ArrayList<>();
// Go through the FastList of region
for (L2WorldRegion regi : reg.getSurroundingRegions())
{
// Go through visible objects of the selected region
Collection vObj = regi.getVisibleObjects().values();
for (L2Object _object : vObj)
{
if ((_object == null) || _object.equals(object))
{
continue; // skip our own character
}
else if (!_object.isVisible())
{
continue; // skip dying objects
}
result.add(_object);
}
}
return result;
}
/**
* Return all visible objects of the L2WorldRegions in the circular area (radius) centered on the object. Concept : All visible object are identified in _visibleObjects of their current L2WorldRegion
* All surrounding L2WorldRegion are identified in _surroundingRegions of the selected L2WorldRegion in order to scan a large area around a L2Object Example of use : Define the aggrolist of monster Define visible objects of a L2Object Skill :
* Confusion...
* @param object L2object that determine the center of the circular area
* @param radius Radius of the circular area
* @return
*/
public List getVisibleObjects(L2Object object, int radius)
{
if ((object == null) || !object.isVisible())
{
return new ArrayList<>();
}
int x = object.getX();
int y = object.getY();
int sqRadius = radius * radius;
// Create an FastList in order to contain all visible L2Object
List result = new ArrayList<>();
// Go through the FastList of region
for (L2WorldRegion regi : object.getWorldRegion().getSurroundingRegions())
{
// Go through visible objects of the selected region
Collection vObj = regi.getVisibleObjects().values();
for (L2Object _object : vObj)
{
if ((_object == null) || _object.equals(object))
{
continue; // skip our own character
}
int x1 = _object.getX();
int y1 = _object.getY();
double dx = x1 - x;
double dy = y1 - y;
if (((dx * dx) + (dy * dy)) < sqRadius)
{
result.add(_object);
}
}
}
return result;
}
/**
* Return all visible objects of the L2WorldRegions in the spheric area (radius) centered on the object. Concept : All visible object are identified in _visibleObjects of their current L2WorldRegion
* All surrounding L2WorldRegion are identified in _surroundingRegions of the selected L2WorldRegion in order to scan a large area around a L2Object Example of use : Define the target list of a skill Define the target list of a polearme attack
* @param object L2object that determine the center of the circular area
* @param radius Radius of the spheric area
* @return
*/
public List getVisibleObjects3D(L2Object object, int radius)
{
if ((object == null) || !object.isVisible())
{
return new ArrayList<>();
}
int x = object.getX();
int y = object.getY();
int z = object.getZ();
int sqRadius = radius * radius;
// Create an FastList in order to contain all visible L2Object
List result = new ArrayList<>();
// Go through visible object of the selected region
for (L2WorldRegion regi : object.getWorldRegion().getSurroundingRegions())
{
Collection vObj = regi.getVisibleObjects().values();
for (L2Object _object : vObj)
{
if ((_object == null) || _object.equals(object))
{
continue; // skip our own character
}
int x1 = _object.getX();
int y1 = _object.getY();
int z1 = _object.getZ();
long dx = x1 - x;
long dy = y1 - y;
long dz = z1 - z;
if (((dx * dx) + (dy * dy) + (dz * dz)) < sqRadius)
{
result.add(_object);
}
}
}
return result;
}
/**
* Concept : All visible object are identified in _visibleObjects of their current L2WorldRegion
* All surrounding L2WorldRegion are identified in _surroundingRegions of the selected L2WorldRegion in order to scan a large area around a L2Object Example of use : Find Close Objects for L2Character
* @param object L2object that determine the current L2WorldRegion
* @return all visible players of the L2WorldRegion object's and of its surrounding L2WorldRegion.
*/
public List getVisiblePlayable(L2Object object)
{
L2WorldRegion reg = object.getWorldRegion();
if (reg == null)
{
return null;
}
// Create an FastList in order to contain all visible L2Object
List result = new ArrayList<>();
// Go through the FastList of region
for (L2WorldRegion regi : reg.getSurroundingRegions())
{
// Create an Iterator to go through the visible L2Object of the L2WorldRegion
Map _allpls = regi.getVisiblePlayable();
Collection _playables = _allpls.values();
// Go through visible object of the selected region
for (L2Playable _object : _playables)
{
if ((_object == null) || _object.equals(object))
{
continue; // skip our own character
}
if (!_object.isVisible())
{
continue; // skip dying objects
}
result.add(_object);
}
}
return result;
}
/**
* Calculate the current L2WorldRegions of the object according to its position (x,y). Example of use : Set position of a new L2Object (drop, spawn...) Update position of a L2Object after a movement
* @param point position of the object
* @return
*/
public L2WorldRegion getRegion(Point3D point)
{
return _worldRegions[(point.getX() >> SHIFT_BY) + OFFSET_X][(point.getY() >> SHIFT_BY) + OFFSET_Y];
}
public L2WorldRegion getRegion(int x, int y)
{
return _worldRegions[(x >> SHIFT_BY) + OFFSET_X][(y >> SHIFT_BY) + OFFSET_Y];
}
/**
* Returns the whole 2d array containing the world regions used by ZoneData.java to setup zones inside the world regions
* @return
*/
public L2WorldRegion[][] getWorldRegions()
{
return _worldRegions;
}
/**
* Check if the current L2WorldRegions of the object is valid according to its position (x,y). Example of use : Init L2WorldRegions
* @param x X position of the object
* @param y Y position of the object
* @return True if the L2WorldRegion is valid
*/
private boolean validRegion(int x, int y)
{
return ((x >= 0) && (x <= REGIONS_X) && (y >= 0) && (y <= REGIONS_Y));
}
/**
* Initialize the world regions.
*/
private void initRegions()
{
_worldRegions = new L2WorldRegion[REGIONS_X + 1][REGIONS_Y + 1];
for (int i = 0; i <= REGIONS_X; i++)
{
for (int j = 0; j <= REGIONS_Y; j++)
{
_worldRegions[i][j] = new L2WorldRegion(i, j);
}
}
for (int x = 0; x <= REGIONS_X; x++)
{
for (int y = 0; y <= REGIONS_Y; y++)
{
for (int a = -1; a <= 1; a++)
{
for (int b = -1; b <= 1; b++)
{
if (validRegion(x + a, y + b))
{
_worldRegions[x + a][y + b].addSurroundingRegion(_worldRegions[x][y]);
}
}
}
}
}
_log.info("L2World: (" + REGIONS_X + " by " + REGIONS_Y + ") World Region Grid set up.");
}
/**
* Deleted all spawns in the world.
*/
public void deleteVisibleNpcSpawns()
{
_log.info("Deleting all visible NPC's.");
for (int i = 0; i <= REGIONS_X; i++)
{
for (int j = 0; j <= REGIONS_Y; j++)
{
_worldRegions[i][j].deleteVisibleNpcSpawns();
}
}
_log.info("All visible NPC's deleted.");
}
/**
* @return the current instance of L2World
*/
public static L2World getInstance()
{
return SingletonHolder._instance;
}
private static class SingletonHolder
{
protected static final L2World _instance = new L2World();
}
}