/*
* 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.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.Territory;
import com.l2jserver.gameserver.ThreadPoolManager;
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.templates.chars.L2NpcTemplate;
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
* @version $Revision: 1.9.2.3.2.8 $ $Date: 2005/03/27 15:29:32 $
*/
public class L2Spawn
{
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;
// private String _location = DEFAULT_LOCATION;
/** The identifier of the location area where L2NpcInstance can be spwaned */
private int _location;
/** 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 X position of the spwan point */
private int _locX;
/** The Y position of the spwan point */
private int _locY;
/** The Z position of the spwan point */
private int _locZ;
/** The heading of L2NpcInstance when they are spawned */
private int _heading;
/** The delay between a L2NpcInstance remove and its re-spawn */
private int _respawnDelay;
/** Minimum delay RaidBoss */
private int _respawnMinDelay;
/** Maximum delay RaidBoss */
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<>();
/** The task launching the function doSpawn() */
class SpawnTask implements Runnable
{
// L2NpcInstance _instance;
// int _objId;
private final L2Npc _oldNpc;
public SpawnTask(/* int objid */L2Npc pOldNpc)
{
// _objId= objid;
_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 :
*
* Set the _template of the L2Spawn Calculate the implementationName used to generate the generic constructor of L2NpcInstance managed by this L2Spawn Create the generic constructor of L2NpcInstance managed by this L2Spawn
*
* @param mobTemplate The L2NpcTemplate to link to this L2Spawn
*/
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.templates.chars.L2NpcTemplate")
};
_constructor = Class.forName("com.l2jserver.gameserver.model.actor.instance." + _template.type + "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 getLocation()
{
return _location;
}
/**
* Return the X position of the spwan point.
*
*/
public int getLocx()
{
return _locX;
}
/**
* Return the Y position of the spwan point.
*
*/
public int getLocy()
{
return _locY;
}
/**
* Return the Z position of the spwan point.
*
*/
public int getLocz()
{
return _locZ;
}
/**
* Return the Itdentifier of the L2NpcInstance manage by this L2Spwan contained in the L2NpcTemplate.
*
*/
public int getNpcid()
{
return _template.npcId;
}
/**
* Return the heading of L2NpcInstance when they are spawned.
*
*/
public int getHeading()
{
return _heading;
}
/**
* Return the delay between a L2NpcInstance remove and its re-spawn.
*
*/
public int getRespawnDelay()
{
return _respawnDelay;
}
/**
* Return Min RaidBoss Spawn delay.
*
*/
public int getRespawnMinDelay()
{
return _respawnMinDelay;
}
/**
* Return Max RaidBoss Spawn delay.
*
*/
public int getRespawnMaxDelay()
{
return _respawnMaxDelay;
}
/**
* Set the maximum number of L2NpcInstance that this L2Spawn can manage.
*
*/
public void setAmount(int amount)
{
_maximumCount = amount;
}
/**
* Set the Identifier of the location area where L2NpcInstance can be spwaned.
*
*/
public void setLocation(int location)
{
_location = location;
}
/**
* Set Minimum Respawn Delay.
*
*/
public void setRespawnMinDelay(int date)
{
_respawnMinDelay = date;
}
/**
* Set Maximum Respawn Delay.
*
*/
public void setRespawnMaxDelay(int date)
{
_respawnMaxDelay = date;
}
/**
* Set the X position of the spwan point.
*
*/
public void setLocx(int locx)
{
_locX = locx;
}
/**
* Set the Y position of the spwan point.
*
*/
public void setLocy(int locy)
{
_locY = locy;
}
/**
* Set the Z position of the spwan point.
*
*/
public void setLocz(int locz)
{
_locZ = locz;
}
/**
* Set the heading of L2NpcInstance when they are spawned.
*
*/
public void setHeading(int heading)
{
_heading = heading;
}
/**
* Set the spawn as 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
*
*/
public void decreaseCount(/* int npcId */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), _respawnDelay);
}
}
/**
* Create the initial spawning and set _doRespawn to True.
*
* @return The number of L2NpcInstance that were spawned
*/
public int init()
{
while (_currentCount < _maximumCount)
{
doSpawn();
}
_doRespawn = true;
return _currentCount;
}
/**
* Create a L2NpcInstance in this L2Spawn.
*
*/
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 thios 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)
*
* Get L2NpcInstance Init parameters and its generate an Identifier Call the constructor of the L2NpcInstance Calculate the random position in the location area (if Locx=0 and Locy=0) or get its exact position from the L2Spawn Set the position of the L2NpcInstance
* Set the HP and MP of the L2NpcInstance to the max Set the heading of the L2NpcInstance (random heading if not defined : value=-1) Link the L2NpcInstance to this L2Spawn Init other values of the L2NpcInstance (ex : from its L2CharTemplate for INT, STR, DEX...)
* and add it in the world Lauch the action OnSpawn fo the L2NpcInstance
*
* Increase the current number of L2NpcInstance managed by this L2Spawn
*
*/
public L2Npc doSpawn(boolean isSummonSpawn)
{
L2Npc mob = null;
try
{
// Check if the L2Spawn is not a L2Pet or L2Minion or L2Decoy spawn
if (_template.type.equalsIgnoreCase("L2Pet") || _template.type.equalsIgnoreCase("L2Decoy") || _template.type.equalsIgnoreCase("L2Trap") || _template.type.equalsIgnoreCase("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.npcId + " 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 ((getLocx() == 0) && (getLocy() == 0))
{
if (getLocation() == 0)
{
return mob;
}
// Calculate the random position in the location area
int p[] = Territory.getInstance().getRandomPoint(getLocation());
// 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 = getLocx();
newlocy = getLocy();
if (Config.GEODATA > 0)
{
newlocz = GeoData.getInstance().getSpawnHeight(newlocx, newlocy, getLocz(), getLocz(), this);
}
else
{
newlocz = getLocz();
}
}
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 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.npcId + " ,at: " + mob.getX() + " x, " + mob.getY() + " y, " + mob.getZ() + " z");
}
// 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);
}
}
}
/**
* @param i delay in seconds
*/
public void setRespawnDelay(int i)
{
if (i < 0)
{
_log.warning("respawn delay is negative for spawn:" + this);
}
if (i < 10)
{
i = 10;
}
_respawnDelay = i * 1000;
}
public L2Npc getLastSpawn()
{
return _lastSpawn;
}
/**
* @param oldNpc
*/
public void respawnNpc(L2Npc oldNpc)
{
if (_doRespawn)
{
oldNpc.refreshID();
/* L2NpcInstance instance = */initializeNpcInstance(oldNpc);
}
}
public L2NpcTemplate getTemplate()
{
return _template;
}
public int getInstanceId()
{
return _instanceId;
}
public void setInstanceId(int instanceId)
{
_instanceId = instanceId;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return "L2Spawn [_template=" + getNpcid() + ", _locX=" + _locX + ", _locY=" + _locY + ", _locZ=" + _locZ + ", _heading=" + _heading + "]";
}
}