/* * 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.lang.reflect.Constructor; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javolution.util.FastList; import com.l2jserver.Config; import com.l2jserver.gameserver.GeoData; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.datatables.TerritoryTable; import com.l2jserver.gameserver.idfactory.IdFactory; import com.l2jserver.gameserver.model.actor.L2Attackable; import com.l2jserver.gameserver.model.actor.L2Character; import com.l2jserver.gameserver.model.actor.L2Npc; import com.l2jserver.gameserver.model.actor.instance.L2MonsterInstance; import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate; import com.l2jserver.gameserver.model.interfaces.IIdentifiable; import com.l2jserver.gameserver.model.interfaces.IPositionable; import com.l2jserver.util.Rnd; /** * This class manages the spawn and respawn of a group of L2NpcInstance that are in the same are and have the same type.
* Concept:
* L2NpcInstance can be spawned either in a random position into a location area (if Lox=0 and Locy=0), either at an exact position.
* The heading of the L2NpcInstance can be a random heading if not defined (value= -1) or an exact heading (ex : merchant...). * @author Nightmare */ public class L2Spawn implements IPositionable, IIdentifiable { protected static final Logger _log = Logger.getLogger(L2Spawn.class.getName()); /** The link on the L2NpcTemplate object containing generic and static properties of this spawn (ex : RewardExp, RewardSP, AggroRange...) */ private L2NpcTemplate _template; /** The maximum number of L2NpcInstance that can manage this L2Spawn */ private int _maximumCount; /** The current number of L2NpcInstance managed by this L2Spawn */ private int _currentCount; /** The current number of SpawnTask in progress or stand by of this L2Spawn */ protected int _scheduledCount; /** The identifier of the location area where L2NpcInstance can be spwaned */ private int _locationId; /** The Location of this NPC spawn. */ private Location _location = new Location(0, 0, 0); /** Minimum respawn delay */ private int _respawnMinDelay; /** Maximum respawn delay */ private int _respawnMaxDelay; private int _instanceId = 0; /** The generic constructor of L2NpcInstance managed by this L2Spawn */ private Constructor _constructor; /** If True a L2NpcInstance is respawned each time that another is killed */ private boolean _doRespawn; /** If true then spawn is custom */ private boolean _customSpawn; private L2Npc _lastSpawn; private static List _spawnListeners = new FastList<>(); private boolean _isNoRndWalk = false; // Is no random walk /** The task launching the function doSpawn() */ class SpawnTask implements Runnable { private final L2Npc _oldNpc; public SpawnTask(L2Npc pOldNpc) { _oldNpc = pOldNpc; } @Override public void run() { try { // doSpawn(); respawnNpc(_oldNpc); } catch (Exception e) { _log.log(Level.WARNING, "", e); } _scheduledCount--; } } /** * Constructor of L2Spawn.
* Concept:
* Each L2Spawn owns generic and static properties (ex : RewardExp, RewardSP, AggroRange...).
* All of those properties are stored in a different L2NpcTemplate for each type of L2Spawn. Each template is loaded once in the server cache memory (reduce memory use).
* When a new instance of L2Spawn is created, server just create a link between the instance and the template.
* This link is stored in _template Each L2NpcInstance is linked to a L2Spawn that manages its spawn and respawn (delay, location...).
* This link is stored in _spawn of the L2NpcInstance.
* Actions:
* * @param mobTemplate The L2NpcTemplate to link to this L2Spawn * @throws SecurityException * @throws ClassNotFoundException * @throws NoSuchMethodException */ public L2Spawn(L2NpcTemplate mobTemplate) throws SecurityException, ClassNotFoundException, NoSuchMethodException { // Set the _template of the L2Spawn _template = mobTemplate; if (_template == null) { return; } // Create the generic constructor of L2NpcInstance managed by this L2Spawn Class[] parameters = { int.class, Class.forName("com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate") }; _constructor = Class.forName("com.l2jserver.gameserver.model.actor.instance." + _template.getType() + "Instance").getConstructor(parameters); } /** * @return the maximum number of L2NpcInstance that this L2Spawn can manage. */ public int getAmount() { return _maximumCount; } /** * @return the Identifier of the location area where L2NpcInstance can be spwaned. */ public int getLocationId() { return _locationId; } @Override public Location getLocation() { return _location; } @Override public int getX() { return _location.getX(); } /** * Set the X position of the spawn point. * @param x the x coordinate */ public void setX(int x) { _location.setX(x); } @Override public int getY() { return _location.getY(); } /** * Set the Y position of the spawn point. * @param y the y coordinate */ public void setY(int y) { _location.setY(y); } @Override public int getZ() { return _location.getZ(); } /** * Set the Z position of the spawn point. * @param z the z coordinate */ public void setZ(int z) { _location.setZ(z); } /** * @return the heading of L2NpcInstance when they are spawned. */ public int getHeading() { return _location.getHeading(); } /** * Set the heading of L2NpcInstance when they are spawned. * @param heading */ public void setHeading(int heading) { _location.setHeading(heading); } /** * Set the XYZ position of the spawn point. * @param loc */ public void setLocation(Location loc) { _location = loc; } /** * Gets the NPC ID. * @return the NPC ID */ @Override public int getId() { return _template.getId(); } /** * @return min respawn delay. */ public int getRespawnMinDelay() { return _respawnMinDelay; } /** * @return max respawn delay. */ public int getRespawnMaxDelay() { return _respawnMaxDelay; } /** * Set the maximum number of L2NpcInstance that this L2Spawn can manage. * @param amount */ public void setAmount(int amount) { _maximumCount = amount; } /** * Set the Identifier of the location area where L2NpcInstance can be spawned. * @param id */ public void setLocationId(int id) { _locationId = id; } /** * Set Minimum Respawn Delay. * @param date */ public void setRespawnMinDelay(int date) { _respawnMinDelay = date; } /** * Set Maximum Respawn Delay. * @param date */ public void setRespawnMaxDelay(int date) { _respawnMaxDelay = date; } /** * Set the spawn as custom.
* @param custom */ public void setCustom(boolean custom) { _customSpawn = custom; } /** * @return type of spawn. */ public boolean isCustom() { return _customSpawn; } /** * Decrease the current number of L2NpcInstance of this L2Spawn and if necessary create a SpawnTask to launch after the respawn Delay. Actions :
  • Decrease the current number of L2NpcInstance of this L2Spawn
  • Check if respawn is possible to prevent multiple respawning * caused by lag
  • Update the current number of SpawnTask in progress or stand by of this L2Spawn
  • Create a new SpawnTask to launch after the respawn Delay
  • Caution : A respawn is possible ONLY if _doRespawn=True and _scheduledCount + * _currentCount < _maximumCount * @param oldNpc */ public void decreaseCount(L2Npc oldNpc) { // sanity check if (_currentCount <= 0) { return; } // Decrease the current number of L2NpcInstance of this L2Spawn _currentCount--; // Check if respawn is possible to prevent multiple respawning caused by lag if (_doRespawn && ((_scheduledCount + _currentCount) < _maximumCount)) { // Update the current number of SpawnTask in progress or stand by of this L2Spawn _scheduledCount++; // Create a new SpawnTask to launch after the respawn Delay // ClientScheduler.getInstance().scheduleLow(new SpawnTask(npcId), _respawnDelay); ThreadPoolManager.getInstance().scheduleGeneral(new SpawnTask(oldNpc), hasRespawnRandom() ? Rnd.get(_respawnMinDelay, _respawnMaxDelay) : _respawnMinDelay); } } /** * Create the initial spawning and set _doRespawn to False, if respawn time set to 0, or set it to True otherwise. * @return The number of L2NpcInstance that were spawned */ public int init() { while (_currentCount < _maximumCount) { doSpawn(); } _doRespawn = _respawnMinDelay != 0; return _currentCount; } /** * Create a L2NpcInstance in this L2Spawn. * @param val * @return */ public L2Npc spawnOne(boolean val) { return doSpawn(val); } /** * @return true if respawn enabled */ public boolean isRespawnEnabled() { return _doRespawn; } /** * Set _doRespawn to False to stop respawn in this L2Spawn. */ public void stopRespawn() { _doRespawn = false; } /** * Set _doRespawn to True to start or restart respawn in this L2Spawn. */ public void startRespawn() { _doRespawn = true; } public L2Npc doSpawn() { return doSpawn(false); } /** * Create the L2NpcInstance, add it to the world and lauch its OnSpawn action.
    * Concept:
    * L2NpcInstance can be spawned either in a random position into a location area (if Lox=0 and Locy=0), either at an exact position.
    * The heading of the L2NpcInstance can be a random heading if not defined (value= -1) or an exact heading (ex : merchant...).
    * Actions for an random spawn into location area: (if Locx=0 and Locy=0) * * @param isSummonSpawn * @return */ public L2Npc doSpawn(boolean isSummonSpawn) { L2Npc mob = null; try { // Check if the L2Spawn is not a L2Pet or L2Minion or L2Decoy spawn if (_template.isType("L2Pet") || _template.isType("L2Decoy") || _template.isType("L2Trap") || _template.isType("L2EffectPoint")) { _currentCount++; return mob; } // Get L2NpcInstance Init parameters and its generate an Identifier Object[] parameters = { IdFactory.getInstance().getNextId(), _template }; // Call the constructor of the L2NpcInstance // (can be a L2ArtefactInstance, L2FriendlyMobInstance, L2GuardInstance, L2MonsterInstance, L2SiegeGuardInstance, L2BoxInstance, // L2FeedableBeastInstance, L2TamedBeastInstance, L2FolkInstance or L2TvTEventNpcInstance) Object tmp = _constructor.newInstance(parameters); ((L2Object) tmp).setInstanceId(_instanceId); // Must be done before object is spawned into visible world if (isSummonSpawn && (tmp instanceof L2Character)) { ((L2Character) tmp).setShowSummonAnimation(isSummonSpawn); } // Check if the Instance is a L2NpcInstance if (!(tmp instanceof L2Npc)) { return mob; } mob = (L2Npc) tmp; return initializeNpcInstance(mob); } catch (Exception e) { _log.log(Level.WARNING, "NPC " + _template.getId() + " class not found", e); } return mob; } /** * @param mob * @return */ private L2Npc initializeNpcInstance(L2Npc mob) { int newlocx, newlocy, newlocz; // If Locx=0 and Locy=0, the L2NpcInstance must be spawned in an area defined by location if ((getX() == 0) && (getY() == 0)) { if (getLocationId() == 0) { return mob; } // Calculate the random position in the location area int p[] = TerritoryTable.getInstance().getRandomPoint(getLocationId()); // Set the calculated position of the L2NpcInstance newlocx = p[0]; newlocy = p[1]; newlocz = GeoData.getInstance().getSpawnHeight(newlocx, newlocy, p[2], p[3], this); } else { // The L2NpcInstance is spawned at the exact position (Lox, Locy, Locz) newlocx = getX(); newlocy = getY(); if (Config.GEODATA > 0) { newlocz = GeoData.getInstance().getSpawnHeight(newlocx, newlocy, getZ(), getZ(), this); } else { newlocz = getZ(); } } mob.stopAllEffects(); mob.setIsDead(false); // Reset decay info mob.setDecayed(false); // Set the HP and MP of the L2NpcInstance to the max mob.setCurrentHpMp(mob.getMaxHp(), mob.getMaxMp()); // Set default value mob.setScriptValue(0); // Set is not random walk default value mob.setIsNoRndWalk(isNoRndWalk()); // Set the heading of the L2NpcInstance (random heading if not defined) if (getHeading() == -1) { mob.setHeading(Rnd.nextInt(61794)); } else { mob.setHeading(getHeading()); } if (mob instanceof L2Attackable) { ((L2Attackable) mob).setChampion(false); } if (Config.L2JMOD_CHAMPION_ENABLE) { // Set champion on next spawn if ((mob instanceof L2MonsterInstance) && !getTemplate().isQuestMonster() && !mob.isRaid() && !((L2MonsterInstance) mob).isRaidMinion() && (Config.L2JMOD_CHAMPION_FREQUENCY > 0) && (mob.getLevel() >= Config.L2JMOD_CHAMP_MIN_LVL) && (mob.getLevel() <= Config.L2JMOD_CHAMP_MAX_LVL) && (Config.L2JMOD_CHAMPION_ENABLE_IN_INSTANCES || (getInstanceId() == 0))) { int random = Rnd.get(100); if (random < Config.L2JMOD_CHAMPION_FREQUENCY) { ((L2Attackable) mob).setChampion(true); } } } // Link the L2NpcInstance to this L2Spawn mob.setSpawn(this); // Init other values of the L2NpcInstance (ex : from its L2CharTemplate for INT, STR, DEX...) and add it in the world as a visible object mob.spawnMe(newlocx, newlocy, newlocz); L2Spawn.notifyNpcSpawned(mob); _lastSpawn = mob; if (Config.DEBUG) { _log.finest("Spawned Mob Id: " + _template.getId() + " , at: X: " + mob.getX() + " Y: " + mob.getY() + " Z: " + mob.getZ()); } // Increase the current number of L2NpcInstance managed by this L2Spawn _currentCount++; return mob; } public static void addSpawnListener(SpawnListener listener) { synchronized (_spawnListeners) { _spawnListeners.add(listener); } } public static void removeSpawnListener(SpawnListener listener) { synchronized (_spawnListeners) { _spawnListeners.remove(listener); } } public static void notifyNpcSpawned(L2Npc npc) { synchronized (_spawnListeners) { for (SpawnListener listener : _spawnListeners) { listener.npcSpawned(npc); } } } /** * Set bounds for random calculation and delay for respawn * @param delay delay in seconds * @param randomInterval random interval in seconds */ public void setRespawnDelay(int delay, int randomInterval) { if (delay != 0) { if (delay < 0) { _log.warning("respawn delay is negative for spawn:" + this); } int minDelay = delay - randomInterval; int maxDelay = delay + randomInterval; _respawnMinDelay = Math.max(10, minDelay) * 1000; _respawnMaxDelay = Math.max(10, maxDelay) * 1000; } else { _respawnMinDelay = 0; _respawnMaxDelay = 0; } } public void setRespawnDelay(int delay) { setRespawnDelay(delay, 0); } public int getRespawnDelay() { return (_respawnMinDelay + _respawnMaxDelay) / 2; } public boolean hasRespawnRandom() { return _respawnMinDelay != _respawnMaxDelay; } public L2Npc getLastSpawn() { return _lastSpawn; } /** * @param oldNpc */ public void respawnNpc(L2Npc oldNpc) { if (_doRespawn) { oldNpc.refreshID(); initializeNpcInstance(oldNpc); } } public L2NpcTemplate getTemplate() { return _template; } public int getInstanceId() { return _instanceId; } public void setInstanceId(int instanceId) { _instanceId = instanceId; } @Override public String toString() { return "L2Spawn [_template=" + getId() + ", _locX=" + getX() + ", _locY=" + getY() + ", _locZ=" + getZ() + ", _heading=" + getHeading() + "]"; } public final boolean isNoRndWalk() { return _isNoRndWalk; } public final void setIsNoRndWalk(boolean value) { _isNoRndWalk = value; } }