/* * This file is part of the L2J Mobius project. * * 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 . */ package com.l2jmobius.gameserver.model; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.logging.Logger; import com.l2jmobius.Config; import com.l2jmobius.gameserver.ai.CtrlEvent; import com.l2jmobius.gameserver.ai.CtrlIntention; import com.l2jmobius.gameserver.ai.L2CharacterAI; import com.l2jmobius.gameserver.data.sql.impl.CharNameTable; import com.l2jmobius.gameserver.model.actor.L2Character; import com.l2jmobius.gameserver.model.actor.L2Npc; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.actor.instance.L2PetInstance; import com.l2jmobius.gameserver.model.events.EventDispatcher; import com.l2jmobius.gameserver.model.events.impl.character.npc.OnNpcCreatureSee; import com.l2jmobius.gameserver.model.interfaces.ILocational; import com.l2jmobius.gameserver.network.serverpackets.DeleteObject; import com.l2jmobius.gameserver.util.Util; 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; /** Bit shift, defines number of regions note, shifting by 15 will result in regions corresponding to map tiles shifting by 11 divides one tile to 16x16 regions. */ public static final int SHIFT_BY = 11; public static final int SHIFT_BY_Z = 10; public static final int TILE_SIZE = 32768; /** Map dimensions */ public static final int TILE_X_MIN = 11; public static final int TILE_Y_MIN = 10; public static final int TILE_X_MAX = 28; public static final int TILE_Y_MAX = 26; public static final int TILE_ZERO_COORD_X = 20; public static final int TILE_ZERO_COORD_Y = 18; public static final int MAP_MIN_X = (TILE_X_MIN - TILE_ZERO_COORD_X) * TILE_SIZE; public static final int MAP_MIN_Y = (TILE_Y_MIN - TILE_ZERO_COORD_Y) * TILE_SIZE; public static final int MAP_MIN_Z = -TILE_SIZE / 2; public static final int MAP_MAX_X = ((TILE_X_MAX - TILE_ZERO_COORD_X) + 1) * TILE_SIZE; public static final int MAP_MAX_Y = ((TILE_Y_MAX - TILE_ZERO_COORD_Y) + 1) * TILE_SIZE; public static final int MAP_MAX_Z = TILE_SIZE / 2; /** 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); public static final int OFFSET_Z = Math.abs(MAP_MIN_Z >> SHIFT_BY_Z); /** 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 static final int REGIONS_Z = (MAP_MAX_Z >> SHIFT_BY_Z) + OFFSET_Z; public static final int REGION_MIN_DIMENSION = Math.min(TILE_SIZE / (TILE_SIZE >> SHIFT_BY_Z), TILE_SIZE / (TILE_SIZE >> SHIFT_BY)); /** Map containing all the players in game. */ private final Map _allPlayers = new ConcurrentHashMap<>(); /** Map containing all the Good players in game. */ private static final Map _allGoodPlayers = new ConcurrentHashMap<>(); /** Map containing all the Evil players in game. */ private static final Map _allEvilPlayers = 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 final L2WorldRegion[][][] _worldRegions = new L2WorldRegion[REGIONS_X + 1][REGIONS_Y + 1][REGIONS_Z + 1]; /** Constructor of L2World. */ protected L2World() { for (int x = 0; x <= REGIONS_X; x++) { for (int y = 0; y <= REGIONS_Y; y++) { for (int z = 0; z <= REGIONS_Z; z++) { _worldRegions[x][y][z] = new L2WorldRegion(x, y, z); } } } _log.info(getClass().getSimpleName() + ": (" + REGIONS_X + " by " + REGIONS_Y + " by " + REGIONS_Z + ") World Region Grid set up."); } /** * 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.warning(getClass().getSimpleName() + ": Current object: " + object + " already exist in OID map!"); // _log.warning(CommonUtil.getTraceString(Thread.currentThread().getStackTrace())); // _log.warning(getClass().getSimpleName() + ": Previous object: " + _allObjects.get(object.getObjectId()) + " already exist in OID map!"); // _log.warning(_allObjectsDebug.get(object.getObjectId())); // _log.warning("---------------------- End ---------------------"); return; } _allObjects.put(object.getObjectId(), object); // _allObjectsDebug.put(object.getObjectId(), CommonUtil.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()); } /** * Example of use: *
    *
  • Client packets : Action, AttackRequest, RequestJoinParty, RequestJoinPledge...
  • *
* @param objectId Identifier of the L2Object * @return the L2Object object that belongs to an ID or null if no object found. */ public L2Object findObject(int objectId) { return _allObjects.get(objectId); } 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 Collection getPlayers() { return _allPlayers.values(); } public Collection getAllGoodPlayers() { return _allGoodPlayers.values(); } public Collection getAllEvilPlayers() { return _allEvilPlayers.values(); } /** * 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 objectId of the player to get Instance * @return the player instance corresponding to the given object ID. */ public L2PcInstance getPlayer(int objectId) { return _allPlayers.get(objectId); } /** * @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 PlayerInstance) are identified in _visibleObjects of his current WorldRegion and in _knownObjects of other surrounding L2Characters
* PlayerInstance are identified in _allPlayers of L2World, in _allPlayers of his current WorldRegion 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 WorldRegion L2Characters

  • *
  • If object is a L2Character, add all surrounding L2Object in its _knownObjects and all surrounding PlayerInstance in its _knownPlayer

  • * * only if object is a PlayerInstance
    * ** only if object is a GM PlayerInstance Caution : This method DOESN'T ADD the object in _visibleObjects and _allPlayers* of WorldRegion (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 PlayerInstance
  • * @param object L2object to add in the world * @param newRegion WorldRegion 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()) { final L2PcInstance player = object.getActingPlayer(); if (!player.isTeleporting()) { final L2PcInstance old = getPlayer(player.getObjectId()); if (old != null) { _log.warning(getClass().getSimpleName() + ": Duplicate character!? Closing both characters (" + player.getName() + ")"); player.logout(); old.logout(); return; } addPlayerToWorld(player); } } if (!newRegion.isActive()) { return; } forEachVisibleObject(object, L2Object.class, 1, wo -> { if (object.isPlayer() && wo.isVisibleFor((L2PcInstance) object)) { wo.sendInfo((L2PcInstance) object); if (wo.isCharacter()) { final L2CharacterAI ai = ((L2Character) wo).getAI(); if (ai != null) { ai.describeStateToPlayer((L2PcInstance) object); if (wo.isMonster()) { if (ai.getIntention() == CtrlIntention.AI_INTENTION_IDLE) { ai.setIntention(CtrlIntention.AI_INTENTION_ACTIVE); } } } } } if (wo.isPlayer() && object.isVisibleFor((L2PcInstance) wo)) { object.sendInfo((L2PcInstance) wo); if (object.isCharacter()) { final L2CharacterAI ai = ((L2Character) object).getAI(); if (ai != null) { ai.describeStateToPlayer((L2PcInstance) wo); if (object.isMonster()) { if (ai.getIntention() == CtrlIntention.AI_INTENTION_IDLE) { ai.setIntention(CtrlIntention.AI_INTENTION_ACTIVE); } } } } } if (wo.isNpc() && object.isCharacter()) { EventDispatcher.getInstance().notifyEventAsync(new OnNpcCreatureSee((L2Npc) wo, (L2Character) object, object.isSummon()), (L2Npc) wo); } if (object.isNpc() && wo.isCharacter()) { EventDispatcher.getInstance().notifyEventAsync(new OnNpcCreatureSee((L2Npc) object, (L2Character) wo, wo.isSummon()), (L2Npc) object); } }); } /** * Adds the player to the world. * @param player the player to add */ public void addPlayerToWorld(L2PcInstance player) { _allPlayers.put(player.getObjectId(), player); if (Config.FACTION_SYSTEM_ENABLED) { addFactionPlayerToWorld(player); } } /** * Remove the player from the world. * @param player the player to remove */ public void removeFromAllPlayers(L2PcInstance player) { _allPlayers.remove(player.getObjectId()); if (Config.FACTION_SYSTEM_ENABLED) { if (player.isGood()) { _allGoodPlayers.remove(player.getObjectId()); } else if (player.isEvil()) { _allEvilPlayers.remove(player.getObjectId()); } } } public static void addFactionPlayerToWorld(L2PcInstance player) { if (player.isGood()) { _allGoodPlayers.put(player.getObjectId(), player); } else if (player.isEvil()) { _allEvilPlayers.put(player.getObjectId(), player); } } /** * Remove a L2Object from the world. Concept : L2Object (including PlayerInstance) are identified in _visibleObjects of his current WorldRegion and in _knownObjects of other surrounding L2Characters
    * PlayerInstance are identified in _allPlayers of L2World, in _allPlayers of his current WorldRegion 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 WorldRegion
  • *
  • Remove the L2Object object from _gmList** of GmListTable
  • *
  • Remove object from _knownObjects and _knownPlayer* of all surrounding WorldRegion L2Characters

  • *
  • If object is a L2Character, remove all L2Object from its _knownObjects and all PlayerInstance from its _knownPlayer
  • Caution : This method DOESN'T REMOVE the object from _allObjects of L2World * only if object is a PlayerInstance
    * ** only if object is a GM PlayerInstance Example of use : *
  • Pickup an Item
  • *
  • Decay a L2Character
  • * @param object L2object to remove from the world * @param oldRegion WorldRegion in which the object was before removing */ public void removeVisibleObject(L2Object object, L2WorldRegion oldRegion) { if (object == null) { return; } if (oldRegion != null) { oldRegion.removeVisibleObject(object); // Go through all surrounding WorldRegion Creatures oldRegion.forEachSurroundingRegion(w -> { for (L2Object wo : w.getVisibleObjects().values()) { if (wo == object) { continue; } if (object.isCharacter()) { final L2Character objectCreature = (L2Character) object; final L2CharacterAI ai = objectCreature.getAI(); if (ai != null) { ai.notifyEvent(CtrlEvent.EVT_FORGET_OBJECT, wo); } if (objectCreature.getTarget() == wo) { objectCreature.setTarget(null); } if (object.isPlayer()) { object.sendPacket(new DeleteObject(wo)); } } if (wo.isCharacter()) { final L2Character woCreature = (L2Character) wo; final L2CharacterAI ai = woCreature.getAI(); if (ai != null) { ai.notifyEvent(CtrlEvent.EVT_FORGET_OBJECT, object); } if (woCreature.getTarget() == object) { woCreature.setTarget(null); } if (wo.isPlayer()) { wo.sendPacket(new DeleteObject(object)); } } } return true; }); if (object.isPlayer()) { final L2PcInstance player = object.getActingPlayer(); if (!player.isTeleporting()) { removeFromAllPlayers(player); } } } } public void switchRegion(L2Object object, L2WorldRegion newRegion) { final L2WorldRegion oldRegion = object.getWorldRegion(); if ((oldRegion == null) || (oldRegion == newRegion)) { return; } oldRegion.forEachSurroundingRegion(w -> { if (!newRegion.isSurroundingRegion(w)) { for (L2Object wo : w.getVisibleObjects().values()) { if (wo == object) { continue; } if (object.isCharacter()) { final L2Character objectCreature = (L2Character) object; final L2CharacterAI ai = objectCreature.getAI(); if (ai != null) { ai.notifyEvent(CtrlEvent.EVT_FORGET_OBJECT, wo); } if (objectCreature.getTarget() == wo) { objectCreature.setTarget(null); } if (object.isPlayer()) { object.sendPacket(new DeleteObject(wo)); } } if (wo.isCharacter()) { final L2Character woCreature = (L2Character) wo; final L2CharacterAI ai = woCreature.getAI(); if (ai != null) { ai.notifyEvent(CtrlEvent.EVT_FORGET_OBJECT, object); } if (woCreature.getTarget() == object) { woCreature.setTarget(null); } if (wo.isPlayer()) { wo.sendPacket(new DeleteObject(object)); } } } } return true; }); newRegion.forEachSurroundingRegion(w -> { if (!oldRegion.isSurroundingRegion(w)) { for (L2Object wo : w.getVisibleObjects().values()) { if ((wo == object) || (wo.getInstanceWorld() != object.getInstanceWorld())) { continue; } if (object.isPlayer() && wo.isVisibleFor((L2PcInstance) object)) { wo.sendInfo((L2PcInstance) object); if (wo.isCharacter()) { final L2CharacterAI ai = ((L2Character) wo).getAI(); if (ai != null) { ai.describeStateToPlayer((L2PcInstance) object); if (wo.isMonster()) { if (ai.getIntention() == CtrlIntention.AI_INTENTION_IDLE) { ai.setIntention(CtrlIntention.AI_INTENTION_ACTIVE); } } } } } if (wo.isPlayer() && object.isVisibleFor((L2PcInstance) wo)) { object.sendInfo((L2PcInstance) wo); if (object.isCharacter()) { final L2CharacterAI ai = ((L2Character) object).getAI(); if (ai != null) { ai.describeStateToPlayer((L2PcInstance) wo); if (object.isMonster()) { if (ai.getIntention() == CtrlIntention.AI_INTENTION_IDLE) { ai.setIntention(CtrlIntention.AI_INTENTION_ACTIVE); } } } } } if (wo.isNpc() && object.isCharacter()) { EventDispatcher.getInstance().notifyEventAsync(new OnNpcCreatureSee((L2Npc) wo, (L2Character) object, object.isSummon()), (L2Npc) wo); } if (object.isNpc() && wo.isCharacter()) { EventDispatcher.getInstance().notifyEventAsync(new OnNpcCreatureSee((L2Npc) object, (L2Character) wo, wo.isSummon()), (L2Npc) object); } } } return true; }); } public void forEachVisibleObject(L2Object object, Class clazz, int depth, Consumer c) { if (object == null) { return; } final L2WorldRegion centerWorldRegion = getRegion(object); if (centerWorldRegion == null) { return; } for (int x = Math.max(centerWorldRegion.getRegionX() - depth, 0); x <= Math.min(centerWorldRegion.getRegionX() + depth, REGIONS_X); x++) { for (int y = Math.max(centerWorldRegion.getRegionY() - depth, 0); y <= Math.min(centerWorldRegion.getRegionY() + depth, REGIONS_Y); y++) { for (int z = Math.max(centerWorldRegion.getRegionZ() - depth, 0); z <= Math.min(centerWorldRegion.getRegionZ() + depth, REGIONS_Z); z++) { for (L2Object visibleObject : _worldRegions[x][y][z].getVisibleObjects().values()) { if ((visibleObject == null) || (visibleObject == object) || !clazz.isInstance(visibleObject)) { continue; } if (visibleObject.getInstanceWorld() != object.getInstanceWorld()) { continue; } c.accept(clazz.cast(visibleObject)); } } } } } public void forEachVisibleObject(L2Object object, Class clazz, Consumer c) { forEachVisibleObject(object, clazz, 1, c); } public void forEachVisibleObjectInRange(L2Object object, Class clazz, int range, Consumer c) { if (object == null) { return; } final L2WorldRegion centerWorldRegion = getRegion(object); if (centerWorldRegion == null) { return; } final int depth = (range / REGION_MIN_DIMENSION) + 1; for (int x = Math.max(centerWorldRegion.getRegionX() - depth, 0); x <= Math.min(centerWorldRegion.getRegionX() + depth, REGIONS_X); x++) { for (int y = Math.max(centerWorldRegion.getRegionY() - depth, 0); y <= Math.min(centerWorldRegion.getRegionY() + depth, REGIONS_Y); y++) { for (int z = Math.max(centerWorldRegion.getRegionZ() - depth, 0); z <= Math.min(centerWorldRegion.getRegionZ() + depth, REGIONS_Z); z++) { final int x1 = (x - OFFSET_X) << SHIFT_BY; final int y1 = (y - OFFSET_Y) << SHIFT_BY; final int z1 = (z - OFFSET_Z) << SHIFT_BY_Z; final int x2 = ((x + 1) - OFFSET_X) << SHIFT_BY; final int y2 = ((y + 1) - OFFSET_Y) << SHIFT_BY; final int z2 = ((z + 1) - OFFSET_Z) << SHIFT_BY_Z; if (Util.cubeIntersectsSphere(x1, y1, z1, x2, y2, z2, object.getX(), object.getY(), object.getZ(), range)) { for (L2Object visibleObject : _worldRegions[x][y][z].getVisibleObjects().values()) { if ((visibleObject == null) || (visibleObject == object) || !clazz.isInstance(visibleObject)) { continue; } if (visibleObject.getInstanceWorld() != object.getInstanceWorld()) { continue; } if (visibleObject.calculateDistance(object, true, false) <= range) { c.accept(clazz.cast(visibleObject)); } } } } } } } public List getVisibleObjects(L2Object object, Class clazz) { final List result = new LinkedList<>(); forEachVisibleObject(object, clazz, result::add); return result; } public List getVisibleObjects(L2Object object, Class clazz, Predicate predicate) { final List result = new LinkedList<>(); forEachVisibleObject(object, clazz, o -> { if (predicate.test(o)) { result.add(o); } }); return result; } public List getVisibleObjects(L2Object object, Class clazz, int range) { final List result = new LinkedList<>(); forEachVisibleObjectInRange(object, clazz, range, result::add); return result; } public List getVisibleObjects(L2Object object, Class clazz, int range, Predicate predicate) { final List result = new LinkedList<>(); forEachVisibleObjectInRange(object, clazz, range, o -> { if (predicate.test(o)) { result.add(o); } }); return result; } /** * Calculate the current WorldRegions 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(ILocational point) { return getRegion(point.getX(), point.getY(), point.getZ()); } public L2WorldRegion getRegion(int x, int y, int z) { try { return _worldRegions[(x >> SHIFT_BY) + OFFSET_X][(y >> SHIFT_BY) + OFFSET_Y][(z >> SHIFT_BY_Z) + OFFSET_Z]; } catch (ArrayIndexOutOfBoundsException e) { _log.warning(getClass().getSimpleName() + ": Incorrect world region X: " + ((x >> SHIFT_BY) + OFFSET_X) + " Y: " + ((y >> SHIFT_BY) + OFFSET_Y) + " Z: " + ((z >> SHIFT_BY_Z) + OFFSET_Z) + " for coordinates x: " + x + " y: " + y + " z: " + z); return null; } } /** * Returns the whole 3d 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 WorldRegions of the object is valid according to its position (x,y). Example of use : *
  • Init WorldRegions

  • * @param x X position of the object * @param y Y position of the object * @param z Z position of the object * @return True if the WorldRegion is valid */ public static boolean validRegion(int x, int y, int z) { return ((x >= 0) && (x <= REGIONS_X) && (y >= 0) && (y <= REGIONS_Y)) && (z >= 0) && (z <= REGIONS_Z); } /** * Deleted all spawns in the world. */ public void deleteVisibleNpcSpawns() { _log.info(getClass().getSimpleName() + ": Deleting all visible NPC's."); for (int x = 0; x <= REGIONS_X; x++) { for (int y = 0; y <= REGIONS_Y; y++) { for (int z = 0; z <= REGIONS_Z; z++) { _worldRegions[x][y][z].deleteVisibleNpcSpawns(); } } } _log.info(getClass().getSimpleName() + ": 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(); } }