/* * Copyright (C) 2004-2015 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.instancemanager; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Calendar; import java.util.Map; import java.util.concurrent.ScheduledFuture; import java.util.logging.Level; import java.util.logging.Logger; import javolution.util.FastMap; import com.l2jserver.Config; import com.l2jserver.L2DatabaseFactory; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.datatables.SpawnTable; import com.l2jserver.gameserver.model.L2Spawn; import com.l2jserver.gameserver.model.StatsSet; import com.l2jserver.gameserver.model.actor.instance.L2RaidBossInstance; import com.l2jserver.util.Rnd; /** * Raid Boss spawn manager. * @author godson */ public class RaidBossSpawnManager { private static final Logger _log = Logger.getLogger(RaidBossSpawnManager.class.getName()); protected static final Map _bosses = new FastMap<>(); protected static final Map _spawns = new FastMap<>(); protected static final Map _storedInfo = new FastMap<>(); protected static final Map> _schedules = new FastMap<>(); public static enum StatusEnum { ALIVE, DEAD, UNDEFINED } /** * Instantiates a new raid boss spawn manager. */ protected RaidBossSpawnManager() { load(); } /** * Load. */ public void load() { _bosses.clear(); _spawns.clear(); _storedInfo.clear(); _schedules.clear(); try (Connection con = L2DatabaseFactory.getInstance().getConnection(); PreparedStatement statement = con.prepareStatement("SELECT * FROM raidboss_spawnlist ORDER BY boss_id"); ResultSet rset = statement.executeQuery()) { while (rset.next()) { final L2Spawn spawnDat = new L2Spawn(rset.getInt("boss_id")); spawnDat.setX(rset.getInt("loc_x")); spawnDat.setY(rset.getInt("loc_y")); spawnDat.setZ(rset.getInt("loc_z")); spawnDat.setAmount(rset.getInt("amount")); spawnDat.setHeading(rset.getInt("heading")); spawnDat.setRespawnDelay(rset.getInt("respawn_delay"), rset.getInt("respawn_random")); addNewSpawn(spawnDat, rset.getLong("respawn_time"), rset.getDouble("currentHP"), rset.getDouble("currentMP"), false); } _log.info(getClass().getSimpleName() + ": Loaded " + _bosses.size() + " Instances"); _log.info(getClass().getSimpleName() + ": Scheduled " + _schedules.size() + " Instances"); } catch (SQLException e) { _log.warning(getClass().getSimpleName() + ": Couldnt load raidboss_spawnlist table"); } catch (Exception e) { _log.log(Level.WARNING, getClass().getSimpleName() + ": Error while initializing RaidBossSpawnManager: " + e.getMessage(), e); } } private static class SpawnSchedule implements Runnable { private static final Logger _log = Logger.getLogger(SpawnSchedule.class.getName()); private final int bossId; /** * Instantiates a new spawn schedule. * @param npcId the npc id */ public SpawnSchedule(int npcId) { bossId = npcId; } @Override public void run() { L2RaidBossInstance raidboss = null; if (bossId == 25328) { raidboss = DayNightSpawnManager.getInstance().handleBoss(_spawns.get(bossId)); } else { raidboss = (L2RaidBossInstance) _spawns.get(bossId).doSpawn(); } if (raidboss != null) { raidboss.setRaidStatus(StatusEnum.ALIVE); final StatsSet info = new StatsSet(); info.set("currentHP", raidboss.getCurrentHp()); info.set("currentMP", raidboss.getCurrentMp()); info.set("respawnTime", 0L); _storedInfo.put(bossId, info); _log.info(getClass().getSimpleName() + ": Spawning Raid Boss " + raidboss.getName()); _bosses.put(bossId, raidboss); } _schedules.remove(bossId); } } /** * Update status. * @param boss the boss * @param isBossDead the is boss dead */ public void updateStatus(L2RaidBossInstance boss, boolean isBossDead) { final StatsSet info = _storedInfo.get(boss.getId()); if (info == null) { return; } if (isBossDead) { boss.setRaidStatus(StatusEnum.DEAD); final int respawnMinDelay = (int) (boss.getSpawn().getRespawnMinDelay() * Config.RAID_MIN_RESPAWN_MULTIPLIER); final int respawnMaxDelay = (int) (boss.getSpawn().getRespawnMaxDelay() * Config.RAID_MAX_RESPAWN_MULTIPLIER); final int respawnDelay = Rnd.get(respawnMinDelay, respawnMaxDelay); final long respawnTime = Calendar.getInstance().getTimeInMillis() + respawnDelay; info.set("currentHP", boss.getMaxHp()); info.set("currentMP", boss.getMaxMp()); info.set("respawnTime", respawnTime); if (!_schedules.containsKey(boss.getId()) && ((respawnMinDelay > 0) || (respawnMaxDelay > 0))) { final Calendar time = Calendar.getInstance(); time.setTimeInMillis(respawnTime); _log.info(getClass().getSimpleName() + ": Updated " + boss.getName() + " respawn time to " + time.getTime()); _schedules.put(boss.getId(), ThreadPoolManager.getInstance().scheduleGeneral(new SpawnSchedule(boss.getId()), respawnDelay)); updateDb(); } } else { boss.setRaidStatus(StatusEnum.ALIVE); info.set("currentHP", boss.getCurrentHp()); info.set("currentMP", boss.getCurrentMp()); info.set("respawnTime", 0L); } _storedInfo.put(boss.getId(), info); } /** * Adds the new spawn. * @param spawnDat the spawn dat * @param respawnTime the respawn time * @param currentHP the current hp * @param currentMP the current mp * @param storeInDb the store in db */ public void addNewSpawn(L2Spawn spawnDat, long respawnTime, double currentHP, double currentMP, boolean storeInDb) { if (spawnDat == null) { return; } if (_spawns.containsKey(spawnDat.getId())) { return; } final int bossId = spawnDat.getId(); final long time = Calendar.getInstance().getTimeInMillis(); SpawnTable.getInstance().addNewSpawn(spawnDat, false); if ((respawnTime == 0L) || (time > respawnTime)) { L2RaidBossInstance raidboss = null; if (bossId == 25328) { raidboss = DayNightSpawnManager.getInstance().handleBoss(spawnDat); } else { raidboss = (L2RaidBossInstance) spawnDat.doSpawn(); } if (raidboss != null) { raidboss.setCurrentHp(currentHP); raidboss.setCurrentMp(currentMP); raidboss.setRaidStatus(StatusEnum.ALIVE); _bosses.put(bossId, raidboss); final StatsSet info = new StatsSet(); info.set("currentHP", currentHP); info.set("currentMP", currentMP); info.set("respawnTime", 0L); _storedInfo.put(bossId, info); } } else { final long spawnTime = respawnTime - Calendar.getInstance().getTimeInMillis(); _schedules.put(bossId, ThreadPoolManager.getInstance().scheduleGeneral(new SpawnSchedule(bossId), spawnTime)); } _spawns.put(bossId, spawnDat); if (storeInDb) { try (Connection con = L2DatabaseFactory.getInstance().getConnection(); PreparedStatement statement = con.prepareStatement("INSERT INTO raidboss_spawnlist (boss_id,amount,loc_x,loc_y,loc_z,heading,respawn_time,currentHp,currentMp) VALUES(?,?,?,?,?,?,?,?,?)")) { statement.setInt(1, spawnDat.getId()); statement.setInt(2, spawnDat.getAmount()); statement.setInt(3, spawnDat.getX()); statement.setInt(4, spawnDat.getY()); statement.setInt(5, spawnDat.getZ()); statement.setInt(6, spawnDat.getHeading()); statement.setLong(7, respawnTime); statement.setDouble(8, currentHP); statement.setDouble(9, currentMP); statement.execute(); } catch (Exception e) { // problem with storing spawn _log.log(Level.WARNING, getClass().getSimpleName() + ": Could not store raidboss #" + bossId + " in the DB:" + e.getMessage(), e); } } } /** * Delete spawn. * @param spawnDat the spawn dat * @param updateDb the update db */ public void deleteSpawn(L2Spawn spawnDat, boolean updateDb) { if (spawnDat == null) { return; } final int bossId = spawnDat.getId(); if (!_spawns.containsKey(bossId)) { return; } SpawnTable.getInstance().deleteSpawn(spawnDat, false); _spawns.remove(bossId); if (_bosses.containsKey(bossId)) { _bosses.remove(bossId); } if (_schedules.containsKey(bossId)) { final ScheduledFuture f = _schedules.remove(bossId); f.cancel(true); } if (_storedInfo.containsKey(bossId)) { _storedInfo.remove(bossId); } if (updateDb) { try (Connection con = L2DatabaseFactory.getInstance().getConnection(); PreparedStatement statement = con.prepareStatement("DELETE FROM raidboss_spawnlist WHERE boss_id=?")) { statement.setInt(1, bossId); statement.execute(); } catch (Exception e) { // problem with deleting spawn _log.log(Level.WARNING, getClass().getSimpleName() + ": Could not remove raidboss #" + bossId + " from DB: " + e.getMessage(), e); } } } /** * Update database. */ private void updateDb() { try (Connection con = L2DatabaseFactory.getInstance().getConnection(); PreparedStatement statement = con.prepareStatement("UPDATE raidboss_spawnlist SET respawn_time = ?, currentHP = ?, currentMP = ? WHERE boss_id = ?")) { for (Integer bossId : _storedInfo.keySet()) { if (bossId == null) { continue; } L2RaidBossInstance boss = _bosses.get(bossId); if (boss == null) { continue; } if (boss.getRaidStatus().equals(StatusEnum.ALIVE)) { updateStatus(boss, false); } StatsSet info = _storedInfo.get(bossId); if (info == null) { continue; } try { statement.setLong(1, info.getLong("respawnTime")); statement.setDouble(2, info.getDouble("currentHP")); statement.setDouble(3, info.getDouble("currentMP")); statement.setInt(4, bossId); statement.executeUpdate(); statement.clearParameters(); } catch (SQLException e) { _log.log(Level.WARNING, getClass().getSimpleName() + ": Couldnt update raidboss_spawnlist table " + e.getMessage(), e); } } } catch (SQLException e) { _log.log(Level.WARNING, getClass().getSimpleName() + ": SQL error while updating RaidBoss spawn to database: " + e.getMessage(), e); } } /** * Gets the all raid boss status. * @return the all raid boss status */ public String[] getAllRaidBossStatus() { final String[] msg = new String[(_bosses == null) ? 0 : _bosses.size()]; if (_bosses == null) { msg[0] = "None"; return msg; } int index = 0; for (int i : _bosses.keySet()) { L2RaidBossInstance boss = _bosses.get(i); msg[index++] = boss.getName() + ": " + boss.getRaidStatus().name(); } return msg; } /** * Gets the raid boss status. * @param bossId the boss id * @return the raid boss status */ public String getRaidBossStatus(int bossId) { String msg = "RaidBoss Status..." + Config.EOL; if (_bosses == null) { msg += "None"; return msg; } if (_bosses.containsKey(bossId)) { final L2RaidBossInstance boss = _bosses.get(bossId); msg += boss.getName() + ": " + boss.getRaidStatus().name(); } return msg; } /** * Gets the raid boss status id. * @param bossId the boss id * @return the raid boss status id */ public StatusEnum getRaidBossStatusId(int bossId) { if (_bosses.containsKey(bossId)) { return _bosses.get(bossId).getRaidStatus(); } else if (_schedules.containsKey(bossId)) { return StatusEnum.DEAD; } else { return StatusEnum.UNDEFINED; } } /** * Notify spawn night boss. * @param raidboss the raidboss */ public void notifySpawnNightBoss(L2RaidBossInstance raidboss) { final StatsSet info = new StatsSet(); info.set("currentHP", raidboss.getCurrentHp()); info.set("currentMP", raidboss.getCurrentMp()); info.set("respawnTime", 0L); raidboss.setRaidStatus(StatusEnum.ALIVE); _storedInfo.put(raidboss.getId(), info); _log.info(getClass().getSimpleName() + ": Spawning Night Raid Boss " + raidboss.getName()); _bosses.put(raidboss.getId(), raidboss); } /** * Checks if the boss is defined. * @param bossId the boss id * @return {@code true} if is defined */ public boolean isDefined(int bossId) { return _spawns.containsKey(bossId); } /** * Gets the bosses. * @return the bosses */ public Map getBosses() { return _bosses; } /** * Gets the spawns. * @return the spawns */ public Map getSpawns() { return _spawns; } /** * Gets the stored info. * @return the stored info */ public Map getStoredInfo() { return _storedInfo; } /** * Saves and clears the raid bosses status, including all schedules. */ public void cleanUp() { updateDb(); _bosses.clear(); if (_schedules != null) { for (Integer bossId : _schedules.keySet()) { ScheduledFuture f = _schedules.get(bossId); f.cancel(true); } _schedules.clear(); } _storedInfo.clear(); _spawns.clear(); } /** * Gets the single instance of RaidBossSpawnManager. * @return single instance of RaidBossSpawnManager */ public static RaidBossSpawnManager getInstance() { return SingletonHolder._instance; } private static class SingletonHolder { protected static final RaidBossSpawnManager _instance = new RaidBossSpawnManager(); } }