/* * 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/>. */ package silentium.gameserver.model; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import javolution.util.FastMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import silentium.commons.utils.Point3D; import silentium.gameserver.model.actor.L2Playable; import silentium.gameserver.model.actor.instance.L2PcInstance; import silentium.gameserver.model.actor.instance.L2PetInstance; import silentium.gameserver.tables.CharNameTable; import silentium.gameserver.tables.GmListTable; public final class L2World { private static Logger _log = LoggerFactory.getLogger(L2World.class.getName()); public static final int SHIFT_BY = 12; private static final int TILE_SIZE = 32768; /** Geodata min/max tiles */ public static final int WORLD_X_MIN = 10; public static final int WORLD_X_MAX = 26; public static final int WORLD_Y_MIN = 10; public static final int WORLD_Y_MAX = 26; /** Map dimensions */ public static final int MAP_MIN_X = (WORLD_X_MIN - 20) * TILE_SIZE; public static final int MAP_MAX_X = (WORLD_X_MAX - 19) * TILE_SIZE; public static final int MAP_MIN_Y = (WORLD_Y_MIN - 18) * TILE_SIZE; public static final int MAP_MAX_Y = (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; private final Map<Integer, L2PcInstance> _allPlayers; private final Map<Integer, L2Object> _allObjects; private final Map<Integer, L2PetInstance> _petsInstance; private L2WorldRegion[][] _worldRegions; protected L2World() { _allPlayers = new FastMap<Integer, L2PcInstance>().shared(); _petsInstance = new FastMap<Integer, L2PetInstance>().shared(); _allObjects = new FastMap<Integer, L2Object>().shared(); initRegions(); } /** * @return the current instance of L2World. */ public static L2World getInstance() { return SingletonHolder._instance; } /** * Add L2Object object in _allObjects. * * @param object * The object to add. */ public void storeObject(L2Object object) { if (_allObjects.containsKey(object.getObjectId())) { _log.warn("[L2World] object: " + object + " already exists in OID map!"); return; } _allObjects.put(object.getObjectId(), object); } public long timeStoreObject(L2Object object) { long time = System.currentTimeMillis(); _allObjects.put(object.getObjectId(), object); time -= System.currentTimeMillis(); return time; } /** * Remove L2Object object from _allObjects of L2World.<BR> * <BR> * <B><U> Example of use </U> :</B><BR> * <BR> * <li>Delete item from inventory, tranfer Item from inventory to warehouse</li> <li>Crystallize item</li> <li>Remove NPC/PC/Pet from the * world</li><BR> * * @param object * L2Object to remove from _allObjects of L2World */ public void removeObject(L2Object object) { _allObjects.remove(Integer.valueOf(object.getObjectId())); } public void removeObjects(List<L2Object> list) { for (L2Object o : list) { if (o != null) _allObjects.remove(Integer.valueOf(o.getObjectId())); // suggestion by whatev } } public void removeObjects(L2Object[] objects) { for (L2Object o : objects) _allObjects.remove(Integer.valueOf(o.getObjectId())); // suggestion by whatev } public long timeRemoveObject(L2Object object) { long time = System.currentTimeMillis(); _allObjects.remove(Integer.valueOf(object.getObjectId())); time -= System.currentTimeMillis(); return time; } /** * @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(Integer.valueOf(oID)); } public long timeFindObject(int objectID) { long time = System.currentTimeMillis(); _allObjects.get(Integer.valueOf(objectID)); time -= System.currentTimeMillis(); return time; } public final Map<Integer, L2Object> getAllVisibleObjects() { return _allObjects; } public final int getAllVisibleObjectsCount() { return _allObjects.size(); } /** * @return a table containing all GMs. */ public static List<L2PcInstance> getAllGMs() { return GmListTable.getInstance().getAllGms(true); } /** * @return a collection containing all players in game. */ public Map<Integer, L2PcInstance> getAllPlayers() { return _allPlayers; } /** * Return how many players are online.<BR> * <BR> * * @return number of online players. */ public int getAllPlayersCount() { return _allPlayers.size(); } /** * @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(Integer.valueOf(playerObjId)); } /** * @param ownerId * ID of the owner * @return the pet instance from the given ownerId. */ public L2PetInstance getPet(int ownerId) { return _petsInstance.get(Integer.valueOf(ownerId)); } /** * @param ownerId * ID of the owner * @param pet * L2PetInstance of the pet * @return the given pet instance from the given ownerId. */ public L2PetInstance addPet(int ownerId, L2PetInstance pet) { return _petsInstance.put(ownerId, pet); } /** * Remove the given pet instance.<BR> * <BR> * * @param ownerId * ID of the owner */ public void removePet(int ownerId) { _petsInstance.remove(Integer.valueOf(ownerId)); } /** * Remove the given pet instance.<BR> * <BR> * * @param pet * the pet to remove */ public void removePet(L2PetInstance pet) { _petsInstance.remove(Integer.valueOf(pet.getOwner().getObjectId())); } /** * Add a L2Object in the world.<BR> * <BR> * <B><U> Concept</U> :</B><BR> * <BR> * L2Object (including L2PcInstance) are identified in <B>_visibleObjects</B> of his current L2WorldRegion and in <B>_knownObjects</B> of * other surrounding L2Characters <BR> * L2PcInstance are identified in <B>_allPlayers</B> of L2World, in <B>_allPlayers</B> of his current L2WorldRegion and in * <B>_knownPlayer</B> of other surrounding L2Characters <BR> * <BR> * <B><U> Actions</U> :</B><BR> * <BR> * <li>Add the L2Object object in _allPlayers* of L2World</li> <li>Add the L2Object object in _gmList** of GmListTable</li> <li>Add object in * _knownObjects and _knownPlayer* of all surrounding L2WorldRegion L2Characters</li><BR> * <li>If object is a L2Character, add all surrounding L2Object in its _knownObjects and all surrounding L2PcInstance in its _knownPlayer</li> * <BR> * <I>* only if object is a L2PcInstance</I><BR> * <I>** only if object is a GM L2PcInstance</I><BR> * <BR> * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T ADD the object in _visibleObjects and _allPlayers* of L2WorldRegion (need * synchronisation)</B></FONT><BR> * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T ADD the object to _allObjects and _allPlayers* of L2World (need * synchronisation)</B></FONT><BR> * <BR> * <B><U> Example of use </U> :</B><BR> * <BR> * <li>Drop an Item</li> <li>Spawn a L2Character</li> <li>Apply Death Penalty of a L2PcInstance</li><BR> * <BR> * * @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) { // If selected L2Object is a L2PcIntance, add it in L2ObjectHashSet(L2PcInstance) _allPlayers of L2World // XXX TODO: this code should be obsoleted by protection in putObject func... if (object instanceof L2PcInstance) { L2PcInstance player = (L2PcInstance) object; if (!player.isTeleporting()) { L2PcInstance tmp = _allPlayers.get(Integer.valueOf(player.getObjectId())); if (tmp != null) { _log.warn("Duplicate character!? Closing both characters (" + player.getName() + ")"); player.logout(); tmp.logout(); return; } _allPlayers.put(player.getObjectId(), player); } } if (!newRegion.isActive()) return; // Get all visible objects contained in the _visibleObjects of // L2WorldRegions in a circular area of 2000 units List<L2Object> visibles = getVisibleObjects(object, 2000); _log.trace("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) { // 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); } } /** * Add the L2PcInstance to _allPlayers of L2World. * * @param cha * The L2PcInstance to add. */ public void addToAllPlayers(L2PcInstance cha) { _allPlayers.put(cha.getObjectId(), cha); } /** * Remove the L2PcInstance from _allPlayers of L2World. * * @param cha * The L2PcInstance to remove. */ public void removeFromAllPlayers(L2PcInstance cha) { _allPlayers.remove(Integer.valueOf(cha.getObjectId())); } /** * Remove a L2Object from the world.<BR> * <BR> * <B><U> Concept</U> :</B><BR> * <BR> * L2Object (including L2PcInstance) are identified in <B>_visibleObjects</B> of his current L2WorldRegion and in <B>_knownObjects</B> of * other surrounding L2Characters <BR> * L2PcInstance are identified in <B>_allPlayers</B> of L2World, in <B>_allPlayers</B> of his current L2WorldRegion and in * <B>_knownPlayer</B> of other surrounding L2Characters <BR> * <BR> * <B><U> Actions</U> :</B><BR> * <BR> * <li>Remove the L2Object object from _allPlayers* of L2World</li> <li>Remove the L2Object object from _visibleObjects and _allPlayers* of * L2WorldRegion</li> <li>Remove the L2Object object from _gmList** of GmListTable</li> <li>Remove object from _knownObjects and * _knownPlayer* of all surrounding L2WorldRegion L2Characters</li><BR> * <li>If object is a L2Character, remove all L2Object from its _knownObjects and all L2PcInstance from its _knownPlayer</li><BR> * <BR> * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T REMOVE the object from _allObjects of L2World</B></FONT><BR> * <BR> * <I>* only if object is a L2PcInstance</I><BR> * <I>** only if object is a GM L2PcInstance</I><BR> * <BR> * <B><U> Example of use </U> :</B><BR> * <BR> * <li>Pickup an Item</li> <li>Decay a L2Character</li><BR> * <BR> * * @param object * L2object to remove from the world * @param oldRegion * L2WorldRegion in wich 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()) { Collection<L2Object> vObj = reg.getVisibleObjects().values(); for (L2Object obj : vObj) { if (obj != null) { obj.getKnownList().removeKnownObject(object); object.getKnownList().removeKnownObject(obj); } } } // 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 instanceof L2PcInstance) { if (!((L2PcInstance) object).isTeleporting()) removeFromAllPlayers((L2PcInstance) object); } } } /** * <B><U> Concept</U> :</B><BR> * <BR> * All visible object are identified in <B>_visibleObjects</B> of their current L2WorldRegion <BR> * All surrounding L2WorldRegion are identified in <B>_surroundingRegions</B> of the selected L2WorldRegion in order to scan a large area * around a L2Object<BR> * <BR> * * @param object * L2object that determine the current L2WorldRegion * @return all visible objects of the L2WorldRegion object's and of its surrounding L2WorldRegion. */ public static List<L2Object> getVisibleObjects(L2Object object) { L2WorldRegion reg = object.getWorldRegion(); if (reg == null) return null; // Create an FastList in order to contain all visible L2Object List<L2Object> result = new ArrayList<>(); // Go through the FastList of region for (L2WorldRegion regi : reg.getSurroundingRegions()) { // Go through visible objects of the selected region Collection<L2Object> vObj = regi.getVisibleObjects().values(); for (L2Object _object : vObj) { if (_object == null || _object.equals(object)) continue; // skip our own character if (!_object.isVisible()) continue; // skip dying objects result.add(_object); } } return result; } /** * <B><U> Concept</U> :</B><BR> * <BR> * All visible object are identified in <B>_visibleObjects</B> of their current L2WorldRegion <BR> * All surrounding L2WorldRegion are identified in <B>_surroundingRegions</B> of the selected L2WorldRegion in order to scan a large area * around a L2Object * * @param object * L2object that determine the center of the circular area * @param radius * Radius of the circular area * @return all visible objects of the L2WorldRegions in the circular area (radius) centered on the object. */ public static List<L2Object> 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<L2Object> result = new ArrayList<>(); // Go through the FastList of region for (L2WorldRegion regi : object.getWorldRegion().getSurroundingRegions()) { // Go through visible objects of the selected region Collection<L2Object> 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 the visible object is inside the circular area add the object to the FastList result if (dx * dx + dy * dy < sqRadius) result.add(_object); } } return result; } /** * <B><U> Concept</U> :</B><BR> * <BR> * All visible object are identified in <B>_visibleObjects</B> of their current L2WorldRegion <BR> * All surrounding L2WorldRegion are identified in <B>_surroundingRegions</B> of the selected L2WorldRegion in order to scan a large area * around a L2Object * * @param object * L2object that determine the center of the circular area * @param radius * Radius of the spheric area * @return all visible objects of the L2WorldRegions in the spheric area (radius) centered on the object. */ public static List<L2Object> 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<L2Object> result = new ArrayList<>(); // Go through visible object of the selected region for (L2WorldRegion regi : object.getWorldRegion().getSurroundingRegions()) { Collection<L2Object> 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; } /** * <B><U> Concept</U> :</B><BR> * <BR> * All visible object are identified in <B>_visibleObjects</B> of their current L2WorldRegion <BR> * All surrounding L2WorldRegion are identified in <B>_surroundingRegions</B> of the selected L2WorldRegion in order to scan a large area * around a L2Object * * @param object * L2object that determine the current L2WorldRegion * @return all visible players of the L2WorldRegion object's and of its surrounding L2WorldRegion. */ public static List<L2Playable> getVisiblePlayable(L2Object object) { L2WorldRegion reg = object.getWorldRegion(); if (reg == null) return null; // Create an FastList in order to contain all visible L2Object List<L2Playable> result = new ArrayList<>(); // Go through the FastList of region for (L2WorldRegion regi : reg.getSurroundingRegions()) { Map<Integer, L2Playable> _allpls = regi.getVisiblePlayable(); Collection<L2Playable> _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()) // GM invisible is different than this... continue; // skip dying objects result.add(_object); } } return result; } /** * @param point * position of the object. * @return the current L2WorldRegion of the object according to its position (x,y). */ 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[][] getAllWorldRegions() { return _worldRegions; } /** * Check if the current L2WorldRegions of the object is valid according to its position (x,y).<BR> * <BR> * <B><U> Example of use </U> :</B><BR> * <BR> * <li>Init L2WorldRegions</li><BR> * * @param x * X position of the object * @param y * Y position of the object * @return True if the L2WorldRegion is valid */ private static boolean validRegion(int x, int y) { return (x >= 0 && x <= REGIONS_X && y >= 0 && y <= REGIONS_Y); } /** * Init each L2WorldRegion and their surrounding table.<BR> * <BR> * <B><U> Concept</U> :</B><BR> * <BR> * All surrounding L2WorldRegion are identified in <B>_surroundingRegions</B> of the selected L2WorldRegion in order to scan a large area * around a L2Object<BR> * <BR> * <B><U> Example of use </U> :</B><BR> * <BR> * <li>Constructor of L2World</li><BR> */ 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: WorldRegion grid (" + REGIONS_X + " by " + REGIONS_Y + ") is now setted up."); } /** * Deleted all spawns in the world. */ public void deleteVisibleNpcSpawns() { _log.info("Deleting all visible NPCs."); for (int i = 0; i <= REGIONS_X; i++) { for (int j = 0; j <= REGIONS_Y; j++) _worldRegions[i][j].deleteVisibleNpcSpawns(); } _log.info("All visibles NPCs are now deleted."); } private static class SingletonHolder { protected static final L2World _instance = new L2World(); } }