diff --git a/L2J_Mobius_Underground/dist/db_installer/sql/game/custom_spawnlist.sql b/L2J_Mobius_Underground/dist/db_installer/sql/game/custom_spawnlist.sql deleted file mode 100644 index 392c292a17..0000000000 --- a/L2J_Mobius_Underground/dist/db_installer/sql/game/custom_spawnlist.sql +++ /dev/null @@ -1,17 +0,0 @@ -DROP TABLE IF EXISTS `custom_spawnlist`; -CREATE TABLE IF NOT EXISTS `custom_spawnlist` ( - `location` varchar(40) NOT NULL DEFAULT '', - `count` tinyint(1) unsigned NOT NULL DEFAULT '0', - `npc_templateid` int(10) unsigned NOT NULL DEFAULT '0', - `locx` mediumint(8) NOT NULL DEFAULT '0', - `locy` mediumint(6) NOT NULL DEFAULT '0', - `locz` mediumint(6) NOT NULL DEFAULT '0', - `randomx` mediumint(6) NOT NULL DEFAULT '0', - `randomy` mediumint(6) NOT NULL DEFAULT '0', - `heading` mediumint(6) NOT NULL DEFAULT '0', - `respawn_delay` mediumint(5) NOT NULL DEFAULT '0', - `respawn_random` mediumint(5) NOT NULL DEFAULT '0', - `loc_id` int(9) NOT NULL DEFAULT '0', - `periodOfDay` tinyint(1) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`npc_templateid`,`locx`,`locy`,`locz`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/L2J_Mobius_Underground/dist/game/config/General.ini b/L2J_Mobius_Underground/dist/game/config/General.ini index a066a7043f..dcdc9bb4da 100644 --- a/L2J_Mobius_Underground/dist/game/config/General.ini +++ b/L2J_Mobius_Underground/dist/game/config/General.ini @@ -647,13 +647,6 @@ SafeEnchantCostMultipiler = 5 # Custom Components # --------------------------------------------------------------------------- -# Default: False -CustomSpawnlistTable = True - -# Option to save GM spawn only in the custom table. -# Default: False -SaveGmSpawnOnCustom = True - # Default: False CustomNpcData = True diff --git a/L2J_Mobius_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminSpawn.java b/L2J_Mobius_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminSpawn.java index 98aa70e3e1..6bc3dce918 100644 --- a/L2J_Mobius_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminSpawn.java +++ b/L2J_Mobius_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminSpawn.java @@ -220,7 +220,6 @@ public class AdminSpawn implements IAdminCommandHandler L2World.getInstance().deleteVisibleNpcSpawns(); // now respawn all NpcData.getInstance().load(); - SpawnTable.getInstance().load(); DBSpawnManager.getInstance().load(); QuestManager.getInstance().reloadAllScripts(); AdminData.getInstance().broadcastMessageToGMs("NPC Respawn completed!"); diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/Config.java b/L2J_Mobius_Underground/java/com/l2jmobius/Config.java index 9844e7950c..277491a166 100644 --- a/L2J_Mobius_Underground/java/com/l2jmobius/Config.java +++ b/L2J_Mobius_Underground/java/com/l2jmobius/Config.java @@ -537,8 +537,6 @@ public final class Config public static boolean JAIL_IS_PVP; public static boolean JAIL_DISABLE_CHAT; public static boolean JAIL_DISABLE_TRANSACTION; - public static boolean CUSTOM_SPAWNLIST_TABLE; - public static boolean SAVE_GMSPAWN_ON_CUSTOM; public static boolean CUSTOM_NPC_DATA; public static boolean CUSTOM_TELEPORT_TABLE; public static boolean CUSTOM_NPCBUFFER_TABLES; @@ -1732,8 +1730,6 @@ public final class Config JAIL_IS_PVP = General.getBoolean("JailIsPvp", false); JAIL_DISABLE_CHAT = General.getBoolean("JailDisableChat", true); JAIL_DISABLE_TRANSACTION = General.getBoolean("JailDisableTransaction", false); - CUSTOM_SPAWNLIST_TABLE = General.getBoolean("CustomSpawnlistTable", false); - SAVE_GMSPAWN_ON_CUSTOM = General.getBoolean("SaveGmSpawnOnCustom", false); CUSTOM_NPC_DATA = General.getBoolean("CustomNpcData", false); CUSTOM_TELEPORT_TABLE = General.getBoolean("CustomTeleportTable", false); CUSTOM_NPCBUFFER_TABLES = General.getBoolean("CustomNpcBufferTables", false); diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/GameServer.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/GameServer.java index 1bb31d5455..463e02f4a2 100644 --- a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/GameServer.java +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/GameServer.java @@ -98,7 +98,6 @@ import com.l2jmobius.gameserver.datatables.BotReportTable; import com.l2jmobius.gameserver.datatables.EventDroplist; import com.l2jmobius.gameserver.datatables.ItemTable; import com.l2jmobius.gameserver.datatables.MerchantPriceConfigTable; -import com.l2jmobius.gameserver.datatables.SpawnTable; import com.l2jmobius.gameserver.handler.ConditionHandler; import com.l2jmobius.gameserver.handler.DailyMissionHandler; import com.l2jmobius.gameserver.handler.EffectHandler; @@ -346,7 +345,6 @@ public class GameServer LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Failed to execute script list!", e); } - SpawnTable.getInstance().load(); SpawnsData.getInstance().init(); DBSpawnManager.getInstance(); diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/data/xml/impl/SpawnsData.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/data/xml/impl/SpawnsData.java index d7b5e9117e..6fe8873c14 100644 --- a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/data/xml/impl/SpawnsData.java +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/data/xml/impl/SpawnsData.java @@ -30,6 +30,7 @@ import java.util.stream.Collectors; import org.w3c.dom.Document; import org.w3c.dom.Node; +import com.l2jmobius.Config; import com.l2jmobius.commons.util.IGameXmlReader; import com.l2jmobius.commons.util.IXmlReader; import com.l2jmobius.gameserver.model.ChanceLocation; @@ -87,6 +88,11 @@ public class SpawnsData implements IGameXmlReader */ public void init() { + if (Config.ALT_DEV_NO_SPAWNS) + { + return; + } + LOGGER.info(getClass().getSimpleName() + ": Initializing spawns..."); _spawns.stream().filter(SpawnTemplate::isSpawningByDefault).forEach(template -> { diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/datatables/SpawnTable.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/datatables/SpawnTable.java index 76ba886efc..c3e4826961 100644 --- a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/datatables/SpawnTable.java +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/datatables/SpawnTable.java @@ -16,10 +16,11 @@ */ package com.l2jmobius.gameserver.datatables; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.Statement; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -28,146 +29,19 @@ import java.util.function.Function; import java.util.logging.Logger; import com.l2jmobius.Config; -import com.l2jmobius.commons.database.DatabaseFactory; import com.l2jmobius.gameserver.data.xml.impl.NpcData; import com.l2jmobius.gameserver.model.L2Spawn; -import com.l2jmobius.gameserver.model.StatsSet; -import com.l2jmobius.gameserver.model.actor.templates.L2NpcTemplate; +import com.l2jmobius.gameserver.model.L2World; /** * Spawn data retriever. - * @author Zoey76 + * @author Zoey76, Mobius */ public final class SpawnTable { private static final Logger LOGGER = Logger.getLogger(SpawnTable.class.getName()); - // SQL - private static final String SELECT_CUSTOM_SPAWNS = "SELECT count, npc_templateid, locx, locy, locz, heading, respawn_delay, respawn_random, loc_id, periodOfDay FROM custom_spawnlist"; - private static final String INSERT_CUSTOM_SPAWN = "INSERT INTO custom_spawnlist (count,npc_templateid,locx,locy,locz,heading,respawn_delay,respawn_random,loc_id) values(?,?,?,?,?,?,?,?,?)"; - private static final String DELETE_CUSTOM_SPAWN = "DELETE FROM custom_spawnlist WHERE locx=? AND locy=? AND locz=? AND npc_templateid=? AND heading=?"; private static final Map> _spawnTable = new ConcurrentHashMap<>(); - /** - * Wrapper to load all spawns. - */ - public void load() - { - if (!Config.ALT_DEV_NO_SPAWNS && Config.CUSTOM_SPAWNLIST_TABLE) - { - fillSpawnTable(); - LOGGER.info(getClass().getSimpleName() + ": Loaded " + _spawnTable.size() + " custom npc spawns."); - } - } - - private boolean checkTemplate(int npcId) - { - final L2NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(npcId); - if (npcTemplate == null) - { - LOGGER.warning(getClass().getSimpleName() + ": Data missing in NPC table for ID: " + npcId + "."); - return false; - } - - if (npcTemplate.isType("L2SiegeGuard") || npcTemplate.isType("L2RaidBoss")) - { - // Don't spawn - return false; - } - - return true; - } - - /** - * Retrieves spawn data from database. - * @return the spawn count - */ - private int fillSpawnTable() - { - int npcSpawnCount = 0; - try (Connection con = DatabaseFactory.getInstance().getConnection(); - Statement s = con.createStatement(); - ResultSet rs = s.executeQuery(SELECT_CUSTOM_SPAWNS)) - { - while (rs.next()) - { - final StatsSet spawnInfo = new StatsSet(); - final int npcId = rs.getInt("npc_templateid"); - - // Check basic requirements first - if (!checkTemplate(npcId)) - { - // Don't spawn - continue; - } - - spawnInfo.set("npcTemplateid", npcId); - spawnInfo.set("count", rs.getInt("count")); - spawnInfo.set("x", rs.getInt("locx")); - spawnInfo.set("y", rs.getInt("locy")); - spawnInfo.set("z", rs.getInt("locz")); - spawnInfo.set("heading", rs.getInt("heading")); - spawnInfo.set("respawnDelay", rs.getInt("respawn_delay")); - spawnInfo.set("respawnRandom", rs.getInt("respawn_random")); - spawnInfo.set("locId", rs.getInt("loc_id")); - spawnInfo.set("periodOfDay", rs.getInt("periodOfDay")); - npcSpawnCount += addSpawn(spawnInfo); - } - } - catch (Exception e) - { - LOGGER.warning(getClass().getSimpleName() + ": Spawn could not be initialized: " + e); - } - return npcSpawnCount; - } - - /** - * Creates NPC spawn - * @param spawnInfo StatsSet of spawn parameters - * @param AIData Map of specific AI parameters for this spawn - * @return count NPC instances, spawned by this spawn - */ - private int addSpawn(StatsSet spawnInfo, Map AIData) - { - L2Spawn spawnDat; - int ret = 0; - try - { - spawnDat = new L2Spawn(spawnInfo.getInt("npcTemplateid")); - spawnDat.setAmount(spawnInfo.getInt("count", 1)); - spawnDat.setX(spawnInfo.getInt("x", 0)); - spawnDat.setY(spawnInfo.getInt("y", 0)); - spawnDat.setZ(spawnInfo.getInt("z", 0)); - spawnDat.setHeading(spawnInfo.getInt("heading", -1)); - spawnDat.setRespawnDelay(spawnInfo.getInt("respawnDelay", 0), spawnInfo.getInt("respawnRandom", 0)); - spawnDat.setLocationId(spawnInfo.getInt("locId", 0)); - final String spawnName = spawnInfo.getString("spawnName", ""); - if (!spawnName.isEmpty()) - { - spawnDat.setName(spawnName); - } - addSpawn(spawnDat); - - ret += spawnDat.init(); - } - catch (Exception e) - { - // problem with initializing spawn, go to next one - LOGGER.warning(getClass().getSimpleName() + ": Spawn could not be initialized: " + e); - } - - return ret; - } - - /** - * Wrapper for {@link #addSpawn(StatsSet, Map)}. - * @param spawnInfo StatsSet of spawn parameters - * @return count NPC instances, spawned by this spawn - */ - private int addSpawn(StatsSet spawnInfo) - { - return addSpawn(spawnInfo, null); - } - /** * Gets the spawn data. * @return the spawn data @@ -210,31 +84,101 @@ public final class SpawnTable /** * Adds a new spawn to the spawn table. * @param spawn the spawn to add - * @param storeInDb if {@code true} it'll be saved in the database + * @param store if {@code true} it'll be saved in the spawn XML files */ - public void addNewSpawn(L2Spawn spawn, boolean storeInDb) + public void addNewSpawn(L2Spawn spawn, boolean store) { addSpawn(spawn); - if (storeInDb) + if (store) { - try (Connection con = DatabaseFactory.getInstance().getConnection(); - PreparedStatement ps = con.prepareStatement(INSERT_CUSTOM_SPAWN)) + // Create output directory if it doesn't exist + final File outputDirectory = new File("data/spawns/Others"); + if (!outputDirectory.exists()) { - ps.setInt(1, spawn.getAmount()); - ps.setInt(2, spawn.getId()); - ps.setInt(3, spawn.getX()); - ps.setInt(4, spawn.getY()); - ps.setInt(5, spawn.getZ()); - ps.setInt(6, spawn.getHeading()); - ps.setInt(7, spawn.getRespawnDelay() / 1000); - ps.setInt(8, spawn.getRespawnMaxDelay() - spawn.getRespawnMinDelay()); - ps.setInt(9, spawn.getLocationId()); - ps.execute(); + boolean result = false; + try + { + outputDirectory.mkdir(); + result = true; + } + catch (SecurityException se) + { + // empty + } + if (result) + { + LOGGER.info(getClass().getSimpleName() + ": Created directory: data/spawns/Others"); + } } - catch (Exception e) + + // XML file for spawn + final int x = ((spawn.getX() - L2World.MAP_MIN_X) >> 15) + L2World.TILE_X_MIN; + final int y = ((spawn.getY() - L2World.MAP_MIN_Y) >> 15) + L2World.TILE_Y_MIN; + final File spawnFile = new File("data/spawns/Others/" + x + "_" + y + ".xml"); + if (spawnFile.isDirectory()) { - LOGGER.warning(getClass().getSimpleName() + ": Could not store spawn in the DB: " + e); + LOGGER.warning(getClass().getSimpleName() + ": Could not save spawn. Target path seems to be a directory."); + return; + } + + // Write info to XML + final String spawnId = String.valueOf(spawn.getId()); + final String spawnCount = String.valueOf(spawn.getAmount()); + final String spawnX = String.valueOf(spawn.getX()); + final String spawnY = String.valueOf(spawn.getY()); + final String spawnZ = String.valueOf(spawn.getZ()); + final String spawnHeading = String.valueOf(spawn.getHeading()); + final String spawnDelay = String.valueOf(spawn.getRespawnDelay() / 1000); + // Update + if (spawnFile.exists()) + { + final File tempFile = new File(spawnFile.getAbsolutePath().substring(Config.DATAPACK_ROOT.getAbsolutePath().length() + 1).replace('\\', '/') + ".tmp"); + try + { + final BufferedReader reader = new BufferedReader(new FileReader(spawnFile)); + final BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + String currentLine; + while ((currentLine = reader.readLine()) != null) + { + if (currentLine.contains("")) + { + writer.write(" 1 ? "\" count=\"" + spawnCount : "") + "\" x=\"" + spawnX + "\" y=\"" + spawnY + "\" z=\"" + spawnZ + (spawn.getHeading() > 0 ? "\" heading=\"" + spawnHeading : "") + "\" respawnTime=\"" + spawnDelay + "sec\" /> " + Config.EOL); + writer.write(currentLine + Config.EOL); + continue; + } + writer.write(currentLine + Config.EOL); + } + writer.close(); + reader.close(); + spawnFile.delete(); + tempFile.renameTo(spawnFile); + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Could not store spawn in the spawn XML files: " + e); + } + } + else // New file + { + try + { + final BufferedWriter writer = new BufferedWriter(new FileWriter(spawnFile)); + writer.write("" + Config.EOL); + writer.write("" + Config.EOL); + writer.write(" " + Config.EOL); + writer.write(" " + Config.EOL); + writer.write(" 1 ? "\" count=\"" + spawnCount : "") + "\" x=\"" + spawnX + "\" y=\"" + spawnY + "\" z=\"" + spawnZ + (spawn.getHeading() > 0 ? "\" heading=\"" + spawnHeading : "") + "\" respawnTime=\"" + spawnDelay + "sec\" /> " + Config.EOL); + writer.write(" " + Config.EOL); + writer.write(" " + Config.EOL); + writer.write("" + Config.EOL); + writer.close(); + LOGGER.info(getClass().getSimpleName() + ": Created file: data/spawns/Others/" + x + "_" + y + ".xml"); + } + catch (Exception e) + { + LOGGER.warning(getClass().getSimpleName() + ": Spawn " + spawn + " could not be added to the spawn XML files: " + e); + } } } } @@ -242,30 +186,84 @@ public final class SpawnTable /** * Delete an spawn from the spawn table. * @param spawn the spawn to delete - * @param updateDb if {@code true} database will be updated + * @param update if {@code true} the spawn XML files will be updated */ - public void deleteSpawn(L2Spawn spawn, boolean updateDb) + public void deleteSpawn(L2Spawn spawn, boolean update) { if (!removeSpawn(spawn)) { return; } - if (updateDb) + if (update) { - try (Connection con = DatabaseFactory.getInstance().getConnection(); - PreparedStatement ps = con.prepareStatement(DELETE_CUSTOM_SPAWN)) + final int x = ((spawn.getX() - L2World.MAP_MIN_X) >> 15) + L2World.TILE_X_MIN; + final int y = ((spawn.getY() - L2World.MAP_MIN_Y) >> 15) + L2World.TILE_Y_MIN; + final File spawnFile = spawn.getNpcSpawnTemplate() != null ? spawn.getNpcSpawnTemplate().getSpawnTemplate().getFile() : new File("data/spawns/Others/" + x + "_" + y + ".xml"); + final File tempFile = new File(spawnFile.getAbsolutePath().substring(Config.DATAPACK_ROOT.getAbsolutePath().length() + 1).replace('\\', '/') + ".tmp"); + try { - ps.setInt(1, spawn.getX()); - ps.setInt(2, spawn.getY()); - ps.setInt(3, spawn.getZ()); - ps.setInt(4, spawn.getId()); - ps.setInt(5, spawn.getHeading()); - ps.execute(); + final BufferedReader reader = new BufferedReader(new FileReader(spawnFile)); + final BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile)); + final String spawnId = String.valueOf(spawn.getId()); + final String spawnX = String.valueOf(spawn.getX()); + final String spawnY = String.valueOf(spawn.getY()); + final String spawnZ = String.valueOf(spawn.getZ()); + boolean found = false; // in XML you can have more than one spawn with same coords + boolean isMultiLine = false; // in case spawn has more stats + boolean lastLineFound = false; // used to check for empty file + int lineCount = 0; + String currentLine; + while ((currentLine = reader.readLine()) != null) + { + if (!found) + { + if (isMultiLine) + { + if (currentLine.contains("")) + { + isMultiLine = false; + found = true; + } + continue; + } + if (currentLine.contains(spawnId) && currentLine.contains(spawnX) && currentLine.contains(spawnY) && currentLine.contains(spawnZ)) + { + if (!currentLine.contains("/>")) + { + isMultiLine = true; + } + else + { + found = true; + } + continue; + } + } + writer.write(currentLine + Config.EOL); + if (currentLine.contains("")) + { + lastLineFound = true; + } + if (!lastLineFound) + { + lineCount++; + } + } + writer.close(); + reader.close(); + spawnFile.delete(); + tempFile.renameTo(spawnFile); + // Delete empty file + if (lineCount < 7) + { + LOGGER.info(getClass().getSimpleName() + ": Deleted empty file: " + spawnFile.getAbsolutePath().substring(Config.DATAPACK_ROOT.getAbsolutePath().length() + 1).replace('\\', '/')); + spawnFile.delete(); + } } catch (Exception e) { - LOGGER.warning(getClass().getSimpleName() + ": Spawn " + spawn + " could not be removed from DB: " + e); + LOGGER.warning(getClass().getSimpleName() + ": Spawn " + spawn + " could not be removed from the spawn XML files: " + e); } } } diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/instancemanager/DBSpawnManager.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/instancemanager/DBSpawnManager.java index 3c979048e4..536525d00a 100644 --- a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/instancemanager/DBSpawnManager.java +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/instancemanager/DBSpawnManager.java @@ -75,6 +75,11 @@ public class DBSpawnManager */ public void load() { + if (Config.ALT_DEV_NO_SPAWNS) + { + return; + } + _npcs.clear(); _spawns.clear(); _storedInfo.clear();