com.l2jfree.gameserver.instancemanager.AutoSpawnManager.java Source code

Java tutorial

Introduction

Here is the source code for com.l2jfree.gameserver.instancemanager.AutoSpawnManager.java

Source

/*
 * 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 com.l2jfree.gameserver.instancemanager;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javolution.util.FastList;
import javolution.util.FastMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.l2jfree.L2DatabaseFactory;
import com.l2jfree.gameserver.Announcements;
import com.l2jfree.gameserver.ThreadPoolManager;
import com.l2jfree.gameserver.datatables.NpcTable;
import com.l2jfree.gameserver.datatables.SpawnTable;
import com.l2jfree.gameserver.gameobjects.L2Npc;
import com.l2jfree.gameserver.gameobjects.templates.L2NpcTemplate;
import com.l2jfree.gameserver.idfactory.IdFactory;
import com.l2jfree.gameserver.model.Location;
import com.l2jfree.gameserver.model.entity.Town;
import com.l2jfree.gameserver.model.world.spawn.L2Spawn;
import com.l2jfree.tools.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 interal 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 AutoSpawnManager {
    protected static Log _log = LogFactory.getLog(AutoSpawnManager.class);

    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 FastMap<Integer, AutoSpawnInstance> _registeredSpawns;
    protected FastMap<Integer, ScheduledFuture<?>> _runningSpawns;
    protected boolean _activeState = true;

    private AutoSpawnManager() {
        _registeredSpawns = new FastMap<Integer, AutoSpawnInstance>();
        _runningSpawns = new FastMap<Integer, ScheduledFuture<?>>();

        restoreSpawnData();
    }

    public static AutoSpawnManager getInstance() {
        return SingletonHolder._instance;
    }

    public final int size() {
        return _registeredSpawns.size();
    }

    public void reload() {
        // stop all timers
        for (ScheduledFuture<?> sf : _runningSpawns.values()) {
            if (sf != null)
                sf.cancel(true);
        }
        // unregister all registered spawns
        for (AutoSpawnInstance asi : _registeredSpawns.values()) {
            if (asi != null)
                this.removeSpawn(asi);
        }

        // create clean list
        _registeredSpawns = new FastMap<Integer, AutoSpawnInstance>();
        _runningSpawns = new FastMap<Integer, ScheduledFuture<?>>();

        // load
        restoreSpawnData();
    }

    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(con);

            // 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();
            }

            statement.close();

            _log.info("AutoSpawnHandler: Loaded " + numLoaded + " spawn group(s) from the database.");
        } catch (Exception e) {
            _log.warn("AutoSpawnHandler: Could not restore spawn data: ", e);
        } finally {
            L2DatabaseFactory.close(con);
        }
    }

    /**
     * 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;
        _registeredSpawns.put(newId, newSpawn);

        setSpawnActive(newSpawn, true);

        if (_log.isDebugEnabled())
            _log.debug("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 AutoSpawnInstance 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 boolean removedSuccessfully
     */
    public boolean removeSpawn(AutoSpawnInstance spawnInst) {
        if (!isSpawnRegistered(spawnInst))
            return false;

        try {
            // Try to remove from the list of registered spawns if it exists.
            _registeredSpawns.remove(spawnInst.getNpcId());

            // Cancel the currently associated running scheduled task.
            ScheduledFuture<?> respawnTask = _runningSpawns.remove(spawnInst._objectId);
            if (respawnTask != null)
                respawnTask.cancel(false);

            if (_log.isDebugEnabled())
                _log.debug("AutoSpawnHandler: Removed auto spawn for NPC ID " + spawnInst._npcId + " (Object ID = "
                        + spawnInst._objectId + ").");
        } catch (Exception e) {
            _log.warn("AutoSpawnHandler: Could not auto spawn for NPC ID " + spawnInst._npcId + " (Object ID = "
                    + spawnInst._objectId + "): ", e);
            return false;
        }

        return true;
    }

    /**
     * Remove a registered spawn from the list, specified by the given spawn object ID.
     * 
     * @param objectId
     * @return boolean removedSuccessfully
     */
    public void removeSpawn(int objectId) {
        removeSpawn(_registeredSpawns.get(objectId));
    }

    /**
     * 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);
                _runningSpawns.put(objectId, spawnTask);
            } else {
                AutoDespawner rd = new AutoDespawner(objectId);
                spawnTask = _runningSpawns.remove(objectId);

                if (spawnTask != null)
                    spawnTask.cancel(false);

                ThreadPoolManager.getInstance().scheduleEffect(rd, 0);
            }

            spawnInst.setSpawnActive(isActive);
        }
    }

    /**
     * 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;

        for (AutoSpawnInstance spawnInst : _registeredSpawns.values())
            setSpawnActive(spawnInst, isActive);

        _activeState = isActive;
    }

    /**
     * Returns the number of milliseconds until the next occurrance of
     * the given spawn.
     * 
     * @param spawnInst
     */
    public final long getTimeToNextSpawn(AutoSpawnInstance spawnInst) {
        int objectId = spawnInst.getObjectId();

        if (!isSpawnRegistered(objectId))
            return -1;

        return _runningSpawns.get(objectId).getDelay(TimeUnit.MILLISECONDS);
    }

    /**
     * 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) {
            if (isSpawnRegistered(id))
                return _registeredSpawns.get(id);
        } else {
            for (AutoSpawnInstance spawnInst : _registeredSpawns.values())
                if (spawnInst.getNpcId() == id)
                    return spawnInst;
        }

        return null;
    }

    public FastMap<Integer, AutoSpawnInstance> getAutoSpawnInstances(int npcId) {
        FastMap<Integer, AutoSpawnInstance> spawnInstList = new FastMap<Integer, AutoSpawnInstance>();

        for (AutoSpawnInstance spawnInst : _registeredSpawns.values())
            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) {
        return _registeredSpawns.containsKey(objectId);
    }

    /**
     * Tests if the specified spawn instance is assigned to an auto spawn.
     * 
     * @param spawnInst
     * @return boolean isAssigned
     */
    public final boolean isSpawnRegistered(AutoSpawnInstance spawnInst) {
        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 final int _objectId;

        public AutoSpawner(int objectId) {
            _objectId = objectId;
        }

        @Override
        public void run() {
            try {
                // Retrieve the required spawn instance for this spawn task.
                AutoSpawnInstance 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 + 1;

                    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.warn("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);
                L2Npc npcInst = null;

                if (spawnInst._spawnCount == 1) {
                    npcInst = newSpawn.doSpawn();
                    npcInst.getPosition().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.getPosition().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);
                    }
                }

                Town town = TownManager.getInstance().getClosestTown(npcInst);
                String nearestTown = "";
                if (town != null)
                    nearestTown = TownManager.getInstance().getTownName(town.getTownId());
                else
                    nearestTown = "None";

                // 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 (_log.isDebugEnabled())
                    _log.debug("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);
                }
            } catch (Exception e) {
                _log.warn("AutoSpawnHandler: An error occurred while initializing spawn instance (Object ID = "
                        + _objectId + "): ", e);
            }
        }
    }

    /**
     * AutoDespawner Class
     * <BR><BR>
     * Simply used as a secondary class for despawning an auto spawn instance.
     * 
     * @author Tempy
     */
    private class AutoDespawner implements Runnable {
        private final int _objectId;

        public AutoDespawner(int objectId) {
            _objectId = objectId;
        }

        @Override
        public void run() {
            try {
                AutoSpawnInstance spawnInst = _registeredSpawns.get(_objectId);

                if (spawnInst == null)
                    return;

                for (L2Npc npcInst : spawnInst.getNPCInstanceList()) {
                    if (npcInst == null)
                        continue;

                    npcInst.deleteMe();

                    SpawnTable.getInstance().deleteSpawn(npcInst.getSpawn(), false);

                    spawnInst.removeNpcInstance(npcInst);

                    if (_log.isDebugEnabled())
                        _log.debug("AutoSpawnHandler: Spawns removed for spawn instance (Object ID = " + _objectId
                                + ").");
                }
            } catch (Exception e) {
                _log.warn("AutoSpawnHandler: An error occurred while despawning spawn (Object ID = " + _objectId
                        + "): ", e);
            }
        }
    }

    /**
     * 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 final FastList<L2Npc> _npcList = new FastList<L2Npc>();
        private final FastList<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(L2Npc npcInst) {
            return _npcList.add(npcInst);
        }

        protected boolean removeNpcInstance(L2Npc 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 L2Npc[] getNPCInstanceList() {
            L2Npc[] ret;
            synchronized (_npcList) {
                ret = new L2Npc[_npcList.size()];
                _npcList.toArray(ret);
            }

            return ret;
        }

        public L2Spawn[] getSpawns() {
            FastList<L2Spawn> npcSpawns = new FastList<L2Spawn>();

            for (L2Npc 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) {
                return null;
            }
        }
    }

    @SuppressWarnings("synthetic-access")
    private static class SingletonHolder {
        protected static final AutoSpawnManager _instance = new AutoSpawnManager();
    }
}