/* 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 2, 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* http://www.gnu.org/copyleft/gpl.html
*/
package com.l2jfrozen.gameserver.model.spawn;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javolution.util.FastList;
import javolution.util.FastMap;
import com.l2jfrozen.Config;
import com.l2jfrozen.gameserver.datatables.csv.MapRegionTable;
import com.l2jfrozen.gameserver.datatables.sql.NpcTable;
import com.l2jfrozen.gameserver.datatables.sql.SpawnTable;
import com.l2jfrozen.gameserver.idfactory.IdFactory;
import com.l2jfrozen.gameserver.model.Location;
import com.l2jfrozen.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jfrozen.gameserver.model.entity.Announcements;
import com.l2jfrozen.gameserver.templates.L2NpcTemplate;
import com.l2jfrozen.gameserver.thread.ThreadPoolManager;
import com.l2jfrozen.util.CloseUtil;
import com.l2jfrozen.util.database.L2DatabaseFactory;
import com.l2jfrozen.util.random.Rnd;
/**
* Auto Spawn Handler Allows spawning of a NPC object based on a timer. (From the official idea used for the Merchant
* and Blacksmith of Mammon) General Usage: - Call registerSpawn() with the parameters listed below. int npcId int[][]
* spawnPoints or specify NULL to add points later. int initialDelay (If < 0 = default value) int respawnDelay (If < 0 =
* default value) int despawnDelay (If < 0 = default value or if = 0, function disabled) spawnPoints is a standard
* two-dimensional int array containing X,Y and Z coordinates. The default respawn/despawn delays are currently every
* hour (as for Mammon on official servers). - The resulting AutoSpawnInstance object represents the newly added spawn
* index. - The internal methods of this object can be used to adjust random spawning, for instance a call to
* setRandomSpawn(1, true); would set the spawn at index 1 to be randomly rather than sequentially-based. - Also they
* can be used to specify the number of NPC instances to spawn using setSpawnCount(), and broadcast a message to all
* users using setBroadcast(). Random Spawning = OFF by default Broadcasting = OFF by default
*
* @author Tempy
*/
public class AutoSpawn
{
protected static final Logger _log = Logger.getLogger(AutoSpawn.class.getName());
private static AutoSpawn _instance;
private static final int DEFAULT_INITIAL_SPAWN = 30000; // 30 seconds after registration
private static final int DEFAULT_RESPAWN = 3600000; // 1 hour in millisecs
private static final int DEFAULT_DESPAWN = 3600000; // 1 hour in millisecs
protected Map<Integer, AutoSpawnInstance> _registeredSpawns;
protected Map<Integer, ScheduledFuture<?>> _runningSpawns;
protected boolean _activeState = true;
private AutoSpawn()
{
_registeredSpawns = new FastMap<Integer, AutoSpawnInstance>();
_runningSpawns = new FastMap<Integer, ScheduledFuture<?>>();
restoreSpawnData();
}
public static AutoSpawn getInstance()
{
if(_instance == null)
{
_instance = new AutoSpawn();
}
return _instance;
}
public int size()
{
synchronized(_registeredSpawns){
return _registeredSpawns.size();
}
}
private void restoreSpawnData()
{
int numLoaded = 0;
Connection con = null;
try
{
PreparedStatement statement = null;
PreparedStatement statement2 = null;
ResultSet rs = null;
ResultSet rs2 = null;
con = L2DatabaseFactory.getInstance().getConnection(false);
// Restore spawn group data, then the location data.
statement = con.prepareStatement("SELECT * FROM random_spawn ORDER BY groupId ASC");
rs = statement.executeQuery();
while(rs.next())
{
// Register random spawn group, set various options on the
// created spawn instance.
AutoSpawnInstance spawnInst = registerSpawn(rs.getInt("npcId"), rs.getInt("initialDelay"), rs.getInt("respawnDelay"), rs.getInt("despawnDelay"));
spawnInst.setSpawnCount(rs.getInt("count"));
spawnInst.setBroadcast(rs.getBoolean("broadcastSpawn"));
spawnInst.setRandomSpawn(rs.getBoolean("randomSpawn"));
numLoaded++;
// Restore the spawn locations for this spawn group/instance.
statement2 = con.prepareStatement("SELECT * FROM random_spawn_loc WHERE groupId=?");
statement2.setInt(1, rs.getInt("groupId"));
rs2 = statement2.executeQuery();
while(rs2.next())
{
// Add each location to the spawn group/instance.
spawnInst.addSpawnLocation(rs2.getInt("x"), rs2.getInt("y"), rs2.getInt("z"), rs2.getInt("heading"));
}
statement2.close();
rs2.close();
statement2 = null;
rs2 = null;
}
statement.close();
rs.close();
statement = null;
rs = null;
if(Config.DEBUG)
{
_log.config("AutoSpawnHandler: Loaded " + numLoaded + " spawn group(s) from the database.");
}
}
catch(Exception e)
{
if(Config.ENABLE_ALL_EXCEPTIONS)
e.printStackTrace();
_log.warning("AutoSpawnHandler: Could not restore spawn data: " + e);
}
finally
{
CloseUtil.close(con);
con = null;
}
}
/**
* Registers a spawn with the given parameters with the spawner, and marks it as active. Returns a AutoSpawnInstance
* containing info about the spawn.
*
* @param npcId
* @param spawnPoints
* @param initialDelay (If < 0 = default value)
* @param respawnDelay (If < 0 = default value)
* @param despawnDelay (If < 0 = default value or if = 0, function disabled)
* @return AutoSpawnInstance spawnInst
*/
public AutoSpawnInstance registerSpawn(int npcId, int[][] spawnPoints, int initialDelay, int respawnDelay, int despawnDelay)
{
if(initialDelay < 0)
{
initialDelay = DEFAULT_INITIAL_SPAWN;
}
if(respawnDelay < 0)
{
respawnDelay = DEFAULT_RESPAWN;
}
if(despawnDelay < 0)
{
despawnDelay = DEFAULT_DESPAWN;
}
AutoSpawnInstance newSpawn = new AutoSpawnInstance(npcId, initialDelay, respawnDelay, despawnDelay);
if(spawnPoints != null)
{
for(int[] spawnPoint : spawnPoints)
{
newSpawn.addSpawnLocation(spawnPoint);
}
}
int newId = IdFactory.getInstance().getNextId();
newSpawn._objectId = newId;
synchronized(_registeredSpawns){
_registeredSpawns.put(newId, newSpawn);
}
setSpawnActive(newSpawn, true);
if(Config.DEBUG)
{
_log.config("AutoSpawnHandler: Registered auto spawn for NPC ID " + npcId + " (Object ID = " + newId + ").");
}
return newSpawn;
}
/**
* Registers a spawn with the given parameters with the spawner, and marks it as active. Returns a AutoSpawnInstance
* containing info about the spawn. <BR>
* <B>Warning:</B> Spawn locations must be specified separately using addSpawnLocation().
*
* @param npcId
* @param initialDelay (If < 0 = default value)
* @param respawnDelay (If < 0 = default value)
* @param despawnDelay (If < 0 = default value or if = 0, function disabled)
* @return spawnInst
*/
public AutoSpawnInstance registerSpawn(int npcId, int initialDelay, int respawnDelay, int despawnDelay)
{
return registerSpawn(npcId, null, initialDelay, respawnDelay, despawnDelay);
}
/**
* Remove a registered spawn from the list, specified by the given spawn instance.
*
* @param spawnInst
* @return removedSuccessfully
*/
public boolean removeSpawn(AutoSpawnInstance spawnInst)
{
synchronized(_registeredSpawns){
if(!_registeredSpawns.containsValue(spawnInst))
return false;
// Try to remove from the list of registered spawns if it exists.
_registeredSpawns.remove(spawnInst.getNpcId());
synchronized(_runningSpawns){
// Cancel the currently associated running scheduled task.
ScheduledFuture<?> respawnTask = _runningSpawns.remove(spawnInst._objectId);
try
{
respawnTask.cancel(false);
}catch(Exception e)
{
if(Config.ENABLE_ALL_EXCEPTIONS)
e.printStackTrace();
_log.warning("AutoSpawnHandler: Could not auto spawn for NPC ID " + spawnInst._npcId + " (Object ID = " + spawnInst._objectId + "): " + e);
return false;
}
}
if(Config.DEBUG)
{
_log.config("AutoSpawnHandler: Removed auto spawn for NPC ID " + spawnInst._npcId + " (Object ID = " + spawnInst._objectId + ").");
}
}
return true;
}
/**
* Remove a registered spawn from the list, specified by the given spawn object ID.
* @param objectId
*/
public void removeSpawn(int objectId)
{
AutoSpawnInstance spawn_inst = null;
synchronized(_registeredSpawns){
spawn_inst = _registeredSpawns.get(objectId);
}
removeSpawn(spawn_inst);
}
/**
* Sets the active state of the specified spawn.
*
* @param spawnInst
* @param isActive
*/
public void setSpawnActive(AutoSpawnInstance spawnInst, boolean isActive)
{
if(spawnInst == null)
return;
int objectId = spawnInst._objectId;
if(isSpawnRegistered(objectId))
{
ScheduledFuture<?> spawnTask = null;
if(isActive)
{
AutoSpawner rs = new AutoSpawner(objectId);
if(spawnInst._desDelay > 0)
{
spawnTask = ThreadPoolManager.getInstance().scheduleEffectAtFixedRate(rs, spawnInst._initDelay, spawnInst._resDelay);
}
else
{
spawnTask = ThreadPoolManager.getInstance().scheduleEffect(rs, spawnInst._initDelay);
}
synchronized(_runningSpawns){
_runningSpawns.put(objectId, spawnTask);
}
rs = null;
}
else
{
AutoDespawner rd = new AutoDespawner(objectId);
synchronized(_runningSpawns){
spawnTask = _runningSpawns.remove(objectId);
}
if(spawnTask != null)
{
spawnTask.cancel(false);
}
ThreadPoolManager.getInstance().scheduleEffect(rd, 0);
rd = null;
}
spawnInst.setSpawnActive(isActive);
spawnTask = null;
}
}
/**
* Sets the active state of all auto spawn instances to that specified, and cancels the scheduled spawn task if
* necessary.
*
* @param isActive
*/
public void setAllActive(boolean isActive)
{
if(_activeState == isActive)
return;
Collection<AutoSpawnInstance> instances;
synchronized(_registeredSpawns){
instances = _registeredSpawns.values();
}
for(AutoSpawnInstance spawnInst : instances)
{
setSpawnActive(spawnInst, isActive);
}
_activeState = isActive;
}
/**
* Returns the number of milliseconds until the next occurrence of the given spawn.
*
* @param spawnInst
* @return
*/
public final long getTimeToNextSpawn(AutoSpawnInstance spawnInst)
{
if(spawnInst == null)
return -1;
int objectId = spawnInst.getObjectId();
synchronized(_runningSpawns){
ScheduledFuture<?> future_task = _runningSpawns.get(objectId);
if(future_task!=null)
return future_task.getDelay(TimeUnit.MILLISECONDS);
}
return -1;
}
/**
* Attempts to return the AutoSpawnInstance associated with the given NPC or Object ID type. <BR>
* Note: If isObjectId == false, returns first instance for the specified NPC ID.
*
* @param id
* @param isObjectId
* @return AutoSpawnInstance spawnInst
*/
public final AutoSpawnInstance getAutoSpawnInstance(int id, boolean isObjectId)
{
if(isObjectId)
return _registeredSpawns.get(id);
Collection<AutoSpawnInstance> instances;
synchronized(_registeredSpawns){
instances = _registeredSpawns.values();
}
for(final AutoSpawnInstance spawnInst : instances)
{
if(spawnInst.getNpcId() == id)
return spawnInst;
}
return null;
}
public Map<Integer, AutoSpawnInstance> getAutoSpawnInstances(int npcId)
{
Map<Integer, AutoSpawnInstance> spawnInstList = new FastMap<Integer, AutoSpawnInstance>();
Collection<AutoSpawnInstance> instances;
synchronized(_registeredSpawns){
instances = _registeredSpawns.values();
}
for(final AutoSpawnInstance spawnInst : instances)
{
if(spawnInst.getNpcId() == npcId)
{
spawnInstList.put(spawnInst.getObjectId(), spawnInst);
}
}
return spawnInstList;
}
/**
* Tests if the specified object ID is assigned to an auto spawn.
*
* @param objectId
* @return boolean isAssigned
*/
public final boolean isSpawnRegistered(int objectId)
{
synchronized(_registeredSpawns){
return _registeredSpawns.containsKey(objectId);
}
}
/**
* Tests if the specified spawn instance is assigned to an auto spawn.
*
* @param spawnInst
* @return boolean isAssigned
*/
public boolean isSpawnRegistered(AutoSpawnInstance spawnInst)
{
synchronized(_registeredSpawns){
return _registeredSpawns.containsValue(spawnInst);
}
}
/**
* AutoSpawner Class <BR>
* <BR>
* This handles the main spawn task for an auto spawn instance, and initializes a despawner if required.
*
* @author Tempy
*/
private class AutoSpawner implements Runnable
{
private int _objectId;
protected AutoSpawner(int objectId)
{
_objectId = objectId;
}
@Override
public void run()
{
try
{
AutoSpawnInstance spawnInst = null;
synchronized(_registeredSpawns){
// Retrieve the required spawn instance for this spawn task.
spawnInst = _registeredSpawns.get(_objectId);
}
// If the spawn is not scheduled to be active, cancel the spawn
// task.
if(!spawnInst.isSpawnActive())
return;
Location[] locationList = spawnInst.getLocationList();
// If there are no set co-ordinates, cancel the spawn task.
if(locationList.length == 0)
{
_log.info("AutoSpawnHandler: No location co-ords specified for spawn instance (Object ID = " + _objectId + ").");
return;
}
int locationCount = locationList.length;
int locationIndex = Rnd.nextInt(locationCount);
/*
* If random spawning is disabled, the spawn at the next set of
* co-ordinates after the last. If the index is greater than the
* number of possible spawns, reset the counter to zero.
*/
if(!spawnInst.isRandomSpawn())
{
locationIndex = spawnInst._lastLocIndex;
locationIndex++;
if(locationIndex == locationCount)
{
locationIndex = 0;
}
spawnInst._lastLocIndex = locationIndex;
}
// Set the X, Y and Z co-ordinates, where this spawn will take
// place.
final int x = locationList[locationIndex].getX();
final int y = locationList[locationIndex].getY();
final int z = locationList[locationIndex].getZ();
final int heading = locationList[locationIndex].getHeading();
// Fetch the template for this NPC ID and create a new spawn.
L2NpcTemplate npcTemp = NpcTable.getInstance().getTemplate(spawnInst.getNpcId());
if(npcTemp == null)
{
_log.warning("Couldnt find NPC id" + spawnInst.getNpcId() + " Try to update your DP");
return;
}
L2Spawn newSpawn = new L2Spawn(npcTemp);
newSpawn.setLocx(x);
newSpawn.setLocy(y);
newSpawn.setLocz(z);
if(heading != -1)
{
newSpawn.setHeading(heading);
}
newSpawn.setAmount(spawnInst.getSpawnCount());
if(spawnInst._desDelay == 0)
{
newSpawn.setRespawnDelay(spawnInst._resDelay);
}
// Add the new spawn information to the spawn table, but do not
// store it.
SpawnTable.getInstance().addNewSpawn(newSpawn, false);
L2NpcInstance npcInst = null;
if(spawnInst._spawnCount == 1)
{
npcInst = newSpawn.doSpawn();
npcInst.setXYZ(npcInst.getX(), npcInst.getY(), npcInst.getZ());
spawnInst.addNpcInstance(npcInst);
}
else
{
for(int i = 0; i < spawnInst._spawnCount; i++)
{
npcInst = newSpawn.doSpawn();
// To prevent spawning of more than one NPC in the exact
// same spot,
// move it slightly by a small random offset.
npcInst.setXYZ(npcInst.getX() + Rnd.nextInt(50), npcInst.getY() + Rnd.nextInt(50), npcInst.getZ());
// Add the NPC instance to the list of managed
// instances.
spawnInst.addNpcInstance(npcInst);
}
}
String nearestTown = MapRegionTable.getInstance().getClosestTownName(npcInst);
// Announce to all players that the spawn has taken place, with
// the nearest town location.
if(spawnInst.isBroadcasting() && (npcInst != null))
{
Announcements.getInstance().announceToAll("The " + npcInst.getName() + " has spawned near " + nearestTown + "!");
}
if(Config.DEBUG)
{
_log.info("AutoSpawnHandler: Spawned NPC ID " + spawnInst.getNpcId() + " at " + x + ", " + y + ", " + z + " (Near " + nearestTown + ") for " + spawnInst.getRespawnDelay() / 60000 + " minute(s).");
}
// If there is no despawn time, do not create a despawn task.
if(spawnInst.getDespawnDelay() > 0)
{
AutoDespawner rd = new AutoDespawner(_objectId);
ThreadPoolManager.getInstance().scheduleAi(rd, spawnInst.getDespawnDelay() - 1000);
rd = null;
}
nearestTown = null;
spawnInst = null;
npcInst = null;
newSpawn = null;
npcTemp = null;
locationList = null;
}
catch(Exception e)
{
_log.warning("AutoSpawnHandler: An error occurred while initializing spawn instance (Object ID = " + _objectId + "): " + e);
e.printStackTrace();
}
}
}
/**
* AutoDespawner Class <BR>
* <BR>
* Simply used as a secondary class for despawning an auto spawn instance.
*
* @author Tempy
*/
private class AutoDespawner implements Runnable
{
private int _objectId;
protected AutoDespawner(int objectId)
{
_objectId = objectId;
}
@Override
public void run()
{
try
{
AutoSpawnInstance spawnInst = null;
synchronized(_registeredSpawns){
spawnInst = _registeredSpawns.get(_objectId);
}
if(spawnInst == null)
{
_log.info("AutoSpawnHandler: No spawn registered for object ID = " + _objectId + ".");
return;
}
L2NpcInstance[] npc_instances = spawnInst.getNPCInstanceList();
if(npc_instances == null){
_log.info("AutoSpawnHandler: No spawn registered");
return;
}
for(L2NpcInstance npcInst : npc_instances)
{
if(npcInst == null)
{
continue;
}
npcInst.deleteMe();
spawnInst.removeNpcInstance(npcInst);
if(Config.DEBUG)
{
_log.info("AutoSpawnHandler: Spawns removed for spawn instance (Object ID = " + _objectId + ").");
}
}
spawnInst = null;
}
catch(Exception e)
{
//if(Config.ENABLE_ALL_EXCEPTIONS)
// e.printStackTrace();
_log.warning("AutoSpawnHandler: An error occurred while despawning spawn (Object ID = " + _objectId + "): " + e);
e.printStackTrace();
}
}
}
/**
* AutoSpawnInstance Class <BR>
* <BR>
* Stores information about a registered auto spawn.
*
* @author Tempy
*/
public class AutoSpawnInstance
{
protected int _objectId;
protected int _spawnIndex;
protected int _npcId;
protected int _initDelay;
protected int _resDelay;
protected int _desDelay;
protected int _spawnCount = 1;
protected int _lastLocIndex = -1;
private List<L2NpcInstance> _npcList = new FastList<L2NpcInstance>();
private List<Location> _locList = new FastList<Location>();
private boolean _spawnActive;
private boolean _randomSpawn = false;
private boolean _broadcastAnnouncement = false;
protected AutoSpawnInstance(int npcId, int initDelay, int respawnDelay, int despawnDelay)
{
_npcId = npcId;
_initDelay = initDelay;
_resDelay = respawnDelay;
_desDelay = despawnDelay;
}
protected void setSpawnActive(boolean activeValue)
{
_spawnActive = activeValue;
}
protected boolean addNpcInstance(L2NpcInstance npcInst)
{
return _npcList.add(npcInst);
}
protected boolean removeNpcInstance(L2NpcInstance npcInst)
{
return _npcList.remove(npcInst);
}
public int getObjectId()
{
return _objectId;
}
public int getInitialDelay()
{
return _initDelay;
}
public int getRespawnDelay()
{
return _resDelay;
}
public int getDespawnDelay()
{
return _desDelay;
}
public int getNpcId()
{
return _npcId;
}
public int getSpawnCount()
{
return _spawnCount;
}
public Location[] getLocationList()
{
return _locList.toArray(new Location[_locList.size()]);
}
public L2NpcInstance[] getNPCInstanceList()
{
L2NpcInstance[] ret;
synchronized (_npcList)
{
ret = new L2NpcInstance[_npcList.size()];
_npcList.toArray(ret);
}
return ret;
}
public L2Spawn[] getSpawns()
{
List<L2Spawn> npcSpawns = new FastList<L2Spawn>();
for(L2NpcInstance npcInst : _npcList)
{
npcSpawns.add(npcInst.getSpawn());
}
return npcSpawns.toArray(new L2Spawn[npcSpawns.size()]);
}
public void setSpawnCount(int spawnCount)
{
_spawnCount = spawnCount;
}
public void setRandomSpawn(boolean randValue)
{
_randomSpawn = randValue;
}
public void setBroadcast(boolean broadcastValue)
{
_broadcastAnnouncement = broadcastValue;
}
public boolean isSpawnActive()
{
return _spawnActive;
}
public boolean isRandomSpawn()
{
return _randomSpawn;
}
public boolean isBroadcasting()
{
return _broadcastAnnouncement;
}
public boolean addSpawnLocation(int x, int y, int z, int heading)
{
return _locList.add(new Location(x, y, z, heading));
}
public boolean addSpawnLocation(int[] spawnLoc)
{
if(spawnLoc.length != 3)
return false;
return addSpawnLocation(spawnLoc[0], spawnLoc[1], spawnLoc[2], -1);
}
public Location removeSpawnLocation(int locIndex)
{
try
{
return _locList.remove(locIndex);
}
catch(IndexOutOfBoundsException e)
{
if(Config.ENABLE_ALL_EXCEPTIONS)
e.printStackTrace();
return null;
}
}
}
}