Single class IdFactory with synchronized static methods.
This commit is contained in:
		| @@ -1,20 +0,0 @@ | ||||
| # --------------------------------------------------------------------------- | ||||
| # ID Factory Settings | ||||
| # --------------------------------------------------------------------------- | ||||
| # Warning:  | ||||
| # Please take extreme caution when changing anything. Also please understand what you are changing before you do so on a live server. | ||||
|  | ||||
| # --------------------------------------------------------------------------- | ||||
| # Standard Settings | ||||
| # --------------------------------------------------------------------------- | ||||
|  | ||||
| # Tell server which IDFactory Class to use: | ||||
| # BITSET = One non compaction method | ||||
| # STACK = Another non compaction method | ||||
| # Default: BITSET | ||||
| IDFactory = BITSET | ||||
|  | ||||
| # Check for bad ids in the database on server boot up. | ||||
| # Much faster load time without it, but may cause problems. | ||||
| # Default: True | ||||
| BadIdChecking = True | ||||
| @@ -55,7 +55,6 @@ import org.w3c.dom.Document; | ||||
| import org.w3c.dom.NamedNodeMap; | ||||
| import org.w3c.dom.Node; | ||||
|  | ||||
| import org.l2jmobius.commons.enums.IdFactoryType; | ||||
| import org.l2jmobius.commons.enums.ServerMode; | ||||
| import org.l2jmobius.commons.util.IXmlReader; | ||||
| import org.l2jmobius.commons.util.PropertiesParser; | ||||
| @@ -97,7 +96,6 @@ public class Config | ||||
| 	private static final String GENERAL_CONFIG_FILE = "./config/General.ini"; | ||||
| 	private static final String GRACIASEEDS_CONFIG_FILE = "./config/GraciaSeeds.ini"; | ||||
| 	private static final String GRANDBOSS_CONFIG_FILE = "./config/GrandBoss.ini"; | ||||
| 	private static final String IDFACTORY_CONFIG_FILE = "./config/IdFactory.ini"; | ||||
| 	private static final String LOGIN_CONFIG_FILE = "./config/LoginServer.ini"; | ||||
| 	private static final String NPC_CONFIG_FILE = "./config/NPC.ini"; | ||||
| 	private static final String PVP_CONFIG_FILE = "./config/PVP.ini"; | ||||
| @@ -809,9 +807,6 @@ public class Config | ||||
| 	public static int MAX_REPUTATION; | ||||
| 	public static int REPUTATION_INCREASE; | ||||
| 	 | ||||
| 	public static IdFactoryType IDFACTORY_TYPE; | ||||
| 	public static boolean BAD_ID_CHECKING; | ||||
| 	 | ||||
| 	public static int[] ENCHANT_BLACKLIST; | ||||
| 	public static boolean DISABLE_OVER_ENCHANTING; | ||||
| 	public static int[] AUGMENTATION_BLACKLIST; | ||||
| @@ -1949,12 +1944,6 @@ public class Config | ||||
| 			TRAINING_CAMP_EXP_MULTIPLIER = trainingCampSettings.getDouble("TrainingCampExpMultiplier", 1.0); | ||||
| 			TRAINING_CAMP_SP_MULTIPLIER = trainingCampSettings.getDouble("TrainingCampSpMultiplier", 1.0); | ||||
| 			 | ||||
| 			// Load IdFactory config file (if exists) | ||||
| 			final PropertiesParser IdFactory = new PropertiesParser(IDFACTORY_CONFIG_FILE); | ||||
| 			 | ||||
| 			IDFACTORY_TYPE = IdFactory.getEnum("IDFactory", IdFactoryType.class, IdFactoryType.BITSET); | ||||
| 			BAD_ID_CHECKING = IdFactory.getBoolean("BadIdChecking", true); | ||||
| 			 | ||||
| 			// Load General config file (if exists) | ||||
| 			final PropertiesParser General = new PropertiesParser(GENERAL_CONFIG_FILE); | ||||
| 			DEFAULT_ACCESS_LEVEL = General.getInt("DefaultAccessLevel", 0); | ||||
|   | ||||
| @@ -1,26 +0,0 @@ | ||||
| /* | ||||
|  * This file is part of the L2J Mobius project. | ||||
|  *  | ||||
|  * 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 org.l2jmobius.commons.enums; | ||||
|  | ||||
| /** | ||||
|  * @author Mobius | ||||
|  */ | ||||
| public enum IdFactoryType | ||||
| { | ||||
| 	BITSET, | ||||
| 	STACK | ||||
| } | ||||
| @@ -209,7 +209,8 @@ public class GameServer | ||||
| 		ThreadPool.init(); | ||||
| 		 | ||||
| 		printSection("IdFactory"); | ||||
| 		if (!IdFactory.getInstance().isInitialized()) | ||||
| 		IdFactory.init(); | ||||
| 		if (!IdFactory.hasInitialized()) | ||||
| 		{ | ||||
| 			LOGGER.severe(getClass().getSimpleName() + ": Could not read object IDs from database. Please check your configuration."); | ||||
| 			throw new Exception("Could not initialize the ID factory!"); | ||||
| @@ -424,7 +425,7 @@ public class GameServer | ||||
| 		 | ||||
| 		Runtime.getRuntime().addShutdownHook(Shutdown.getInstance()); | ||||
| 		 | ||||
| 		LOGGER.info("IdFactory: Free ObjectID's remaining: " + IdFactory.getInstance().size()); | ||||
| 		LOGGER.info("IdFactory: Free ObjectID's remaining: " + IdFactory.size()); | ||||
| 		 | ||||
| 		if ((Config.OFFLINE_TRADE_ENABLE || Config.OFFLINE_CRAFT_ENABLE) && Config.RESTORE_OFFLINERS) | ||||
| 		{ | ||||
|   | ||||
| @@ -188,7 +188,7 @@ public class ClanTable | ||||
| 			return null; | ||||
| 		} | ||||
| 		 | ||||
| 		final Clan clan = new Clan(IdFactory.getInstance().getNextId(), clanName); | ||||
| 		final Clan clan = new Clan(IdFactory.getNextId(), clanName); | ||||
| 		final ClanMember leader = new ClanMember(clan, player); | ||||
| 		clan.setLeader(leader); | ||||
| 		leader.setPlayerInstance(player); | ||||
| @@ -260,7 +260,7 @@ public class ClanTable | ||||
| 		} | ||||
| 		 | ||||
| 		_clans.remove(clanId); | ||||
| 		IdFactory.getInstance().releaseId(clanId); | ||||
| 		IdFactory.releaseId(clanId); | ||||
| 		 | ||||
| 		try (Connection con = DatabaseFactory.getConnection()) | ||||
| 		{ | ||||
|   | ||||
| @@ -210,7 +210,7 @@ public class ItemTable | ||||
| 	public ItemInstance createItem(String process, int itemId, long count, Creature actor, Object reference) | ||||
| 	{ | ||||
| 		// Create and Init the ItemInstance corresponding to the Item Identifier | ||||
| 		final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), itemId); | ||||
| 		final ItemInstance item = new ItemInstance(IdFactory.getNextId(), itemId); | ||||
| 		 | ||||
| 		if (process.equalsIgnoreCase("loot") && !Config.AUTO_LOOT_ITEM_IDS.contains(itemId)) | ||||
| 		{ | ||||
| @@ -328,7 +328,7 @@ public class ItemTable | ||||
| 			item.setLastChange(ItemInstance.REMOVED); | ||||
| 			 | ||||
| 			World.getInstance().removeObject(item); | ||||
| 			IdFactory.getInstance().releaseId(item.getObjectId()); | ||||
| 			IdFactory.releaseId(item.getObjectId()); | ||||
| 			 | ||||
| 			if (Config.LOG_ITEMS) | ||||
| 			{ | ||||
|   | ||||
| @@ -1,151 +0,0 @@ | ||||
| /* | ||||
|  * This file is part of the L2J Mobius project. | ||||
|  *  | ||||
|  * 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 org.l2jmobius.gameserver.idfactory; | ||||
|  | ||||
| import java.util.BitSet; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
|  | ||||
| import org.l2jmobius.commons.concurrent.ThreadPool; | ||||
| import org.l2jmobius.gameserver.util.PrimeFinder; | ||||
|  | ||||
| /** | ||||
|  * This class .. | ||||
|  * @version $Revision: 1.2 $ $Date: 2004/06/27 08:12:59 $ | ||||
|  */ | ||||
| public class BitSetIDFactory extends IdFactory | ||||
| { | ||||
| 	private BitSet _freeIds; | ||||
| 	private AtomicInteger _freeIdCount; | ||||
| 	private AtomicInteger _nextFreeId; | ||||
| 	 | ||||
| 	protected class BitSetCapacityCheck implements Runnable | ||||
| 	{ | ||||
| 		@Override | ||||
| 		public void run() | ||||
| 		{ | ||||
| 			synchronized (BitSetIDFactory.this) | ||||
| 			{ | ||||
| 				if (reachingBitSetCapacity()) | ||||
| 				{ | ||||
| 					increaseBitSetCapacity(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	protected BitSetIDFactory() | ||||
| 	{ | ||||
| 		super(); | ||||
| 		 | ||||
| 		synchronized (BitSetIDFactory.class) | ||||
| 		{ | ||||
| 			ThreadPool.scheduleAtFixedRate(new BitSetCapacityCheck(), 30000, 30000); | ||||
| 			initialize(); | ||||
| 		} | ||||
| 		LOGGER.info(getClass().getSimpleName() + ": " + _freeIds.size() + " id's available."); | ||||
| 	} | ||||
| 	 | ||||
| 	public void initialize() | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			_freeIds = new BitSet(PrimeFinder.nextPrime(100000)); | ||||
| 			_freeIds.clear(); | ||||
| 			_freeIdCount = new AtomicInteger(FREE_OBJECT_ID_SIZE); | ||||
| 			 | ||||
| 			for (int usedObjectId : extractUsedObjectIDTable()) | ||||
| 			{ | ||||
| 				final int objectID = usedObjectId - FIRST_OID; | ||||
| 				if (objectID < 0) | ||||
| 				{ | ||||
| 					LOGGER.warning(getClass().getSimpleName() + ": Object ID " + usedObjectId + " in DB is less than minimum ID of " + FIRST_OID); | ||||
| 					continue; | ||||
| 				} | ||||
| 				_freeIds.set(usedObjectId - FIRST_OID); | ||||
| 				_freeIdCount.decrementAndGet(); | ||||
| 			} | ||||
| 			 | ||||
| 			_nextFreeId = new AtomicInteger(_freeIds.nextClearBit(0)); | ||||
| 			_initialized = true; | ||||
| 		} | ||||
| 		catch (Exception e) | ||||
| 		{ | ||||
| 			_initialized = false; | ||||
| 			LOGGER.severe(getClass().getSimpleName() + ": Could not be initialized properly: " + e.getMessage()); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public synchronized void releaseId(int objectID) | ||||
| 	{ | ||||
| 		if ((objectID - FIRST_OID) > -1) | ||||
| 		{ | ||||
| 			_freeIds.clear(objectID - FIRST_OID); | ||||
| 			_freeIdCount.incrementAndGet(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			LOGGER.warning(getClass().getSimpleName() + ": Release objectID " + objectID + " failed (< " + FIRST_OID + ")"); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public synchronized int getNextId() | ||||
| 	{ | ||||
| 		final int newID = _nextFreeId.get(); | ||||
| 		_freeIds.set(newID); | ||||
| 		_freeIdCount.decrementAndGet(); | ||||
| 		 | ||||
| 		final int nextFree = _freeIds.nextClearBit(newID) < 0 ? _freeIds.nextClearBit(0) : _freeIds.nextClearBit(newID); | ||||
| 		 | ||||
| 		if (nextFree < 0) | ||||
| 		{ | ||||
| 			if (_freeIds.size() >= FREE_OBJECT_ID_SIZE) | ||||
| 			{ | ||||
| 				throw new NullPointerException("Ran out of valid Id's."); | ||||
| 			} | ||||
| 			increaseBitSetCapacity(); | ||||
| 		} | ||||
| 		 | ||||
| 		_nextFreeId.set(nextFree); | ||||
| 		 | ||||
| 		return newID + FIRST_OID; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public synchronized int size() | ||||
| 	{ | ||||
| 		return _freeIdCount.get(); | ||||
| 	} | ||||
| 	 | ||||
| 	protected synchronized int usedIdCount() | ||||
| 	{ | ||||
| 		return _freeIdCount.get() - FIRST_OID; | ||||
| 	} | ||||
| 	 | ||||
| 	protected synchronized boolean reachingBitSetCapacity() | ||||
| 	{ | ||||
| 		return PrimeFinder.nextPrime((usedIdCount() * 11) / 10) > _freeIds.size(); | ||||
| 	} | ||||
| 	 | ||||
| 	protected synchronized void increaseBitSetCapacity() | ||||
| 	{ | ||||
| 		final BitSet newBitSet = new BitSet(PrimeFinder.nextPrime((usedIdCount() * 11) / 10)); | ||||
| 		newBitSet.or(_freeIds); | ||||
| 		_freeIds = newBitSet; | ||||
| 	} | ||||
| } | ||||
| @@ -19,23 +19,25 @@ package org.l2jmobius.gameserver.idfactory; | ||||
| import java.sql.Connection; | ||||
| import java.sql.PreparedStatement; | ||||
| import java.sql.ResultSet; | ||||
| import java.sql.SQLException; | ||||
| import java.sql.Statement; | ||||
| import java.util.ArrayList; | ||||
| import java.util.BitSet; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.logging.Level; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| import java.util.logging.Logger; | ||||
|  | ||||
| import org.l2jmobius.Config; | ||||
| import org.l2jmobius.commons.concurrent.ThreadPool; | ||||
| import org.l2jmobius.commons.database.DatabaseFactory; | ||||
| import org.l2jmobius.gameserver.util.PrimeFinder; | ||||
|  | ||||
| /** | ||||
|  * @version $Revision: 1.3.2.1.2.7 $ $Date: 2005/04/11 10:06:12 $ | ||||
|  * @author Mobius (reworked from L2J version) | ||||
|  */ | ||||
| public abstract class IdFactory | ||||
| { | ||||
| 	protected final Logger LOGGER = Logger.getLogger(getClass().getName()); | ||||
| 	private static final Logger LOGGER = Logger.getLogger(IdFactory.class.getName()); | ||||
| 	 | ||||
| 	protected static final String[] ID_CHECKS = | ||||
| 	{ | ||||
| @@ -64,7 +66,6 @@ public abstract class IdFactory | ||||
| 		"SELECT object_id   FROM itemsonground        WHERE object_id >= ?   AND object_id < ?", | ||||
| 		"SELECT summonId	FROM characters_summons	WHERE summonId >= ?	AND summonId < ?" | ||||
| 	}; | ||||
| 	 | ||||
| 	//@formatter:off | ||||
| 	private static final String[][] ID_EXTRACTS = | ||||
| 	{ | ||||
| @@ -75,183 +76,121 @@ public abstract class IdFactory | ||||
| 		{"messages","messageId"} | ||||
| 	}; | ||||
| 	//@formatter:on | ||||
| 	 | ||||
| 	private static final String[] TIMESTAMPS_CLEAN = | ||||
| 	{ | ||||
| 		"DELETE FROM character_instance_time WHERE time <= ?", | ||||
| 		"DELETE FROM character_skills_save WHERE restore_type = 1 AND systime <= ?" | ||||
| 	}; | ||||
| 	 | ||||
| 	protected boolean _initialized; | ||||
| 	 | ||||
| 	public static final int FIRST_OID = 0x10000000; | ||||
| 	public static final int LAST_OID = 0x7FFFFFFF; | ||||
| 	public static final int FREE_OBJECT_ID_SIZE = LAST_OID - FIRST_OID; | ||||
| 	 | ||||
| 	protected static final IdFactory _instance; | ||||
| 	private static BitSet _freeIds; | ||||
| 	private static AtomicInteger _freeIdCount; | ||||
| 	private static AtomicInteger _nextFreeId; | ||||
| 	private static boolean _initialized; | ||||
| 	 | ||||
| 	protected IdFactory() | ||||
| 	{ | ||||
| 		setAllCharacterOffline(); | ||||
| 		if (Config.DATABASE_CLEAN_UP) | ||||
| 		{ | ||||
| 			cleanUpDB(); | ||||
| 		} | ||||
| 		cleanUpTimeStamps(); | ||||
| 	} | ||||
| 	 | ||||
| 	static | ||||
| 	{ | ||||
| 		switch (Config.IDFACTORY_TYPE) | ||||
| 		{ | ||||
| 			case BITSET: | ||||
| 			{ | ||||
| 				_instance = new BitSetIDFactory(); | ||||
| 				break; | ||||
| 			} | ||||
| 			case STACK: | ||||
| 			{ | ||||
| 				_instance = new StackIDFactory(); | ||||
| 				break; | ||||
| 			} | ||||
| 			default: | ||||
| 			{ | ||||
| 				_instance = null; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Sets all character offline | ||||
| 	 */ | ||||
| 	private void setAllCharacterOffline() | ||||
| 	public static void init() | ||||
| 	{ | ||||
| 		// Update characters online status. | ||||
| 		try (Connection con = DatabaseFactory.getConnection(); | ||||
| 			Statement s = con.createStatement()) | ||||
| 		{ | ||||
| 			s.executeUpdate("UPDATE characters SET online = 0"); | ||||
| 			LOGGER.info("Updated characters online status."); | ||||
| 		} | ||||
| 		catch (SQLException e) | ||||
| 		catch (Exception e) | ||||
| 		{ | ||||
| 			LOGGER.log(Level.WARNING, "Could not update characters online status: " + e.getMessage(), e); | ||||
| 			LOGGER.warning("IdFactory: Could not update characters online status: " + e); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * Cleans up Database | ||||
| 	 */ | ||||
| 	private void cleanUpDB() | ||||
| 	{ | ||||
| 		try (Connection con = DatabaseFactory.getConnection(); | ||||
| 			Statement stmt = con.createStatement()) | ||||
| 		 | ||||
| 		// Cleanup database. | ||||
| 		if (Config.DATABASE_CLEAN_UP) | ||||
| 		{ | ||||
| 			final long cleanupStart = System.currentTimeMillis(); | ||||
| 			int cleanCount = 0; | ||||
| 			// Misc/Account Related | ||||
| 			// Please read the descriptions above each before uncommenting them. If you are still | ||||
| 			// unsure of what exactly it does, leave it commented out. This is for those who know | ||||
| 			// what they are doing. :) | ||||
| 			 | ||||
| 			// Deletes only accounts that HAVE been logged into and have no characters associated | ||||
| 			// with the account. | ||||
| 			// cleanCount += | ||||
| 			// stmt.executeUpdate("DELETE FROM accounts WHERE accounts.lastactive > 0 AND accounts.login NOT IN (SELECT account_name FROM characters);"); | ||||
| 			 | ||||
| 			// Deletes any accounts that don't have characters. Whether or not the player has ever | ||||
| 			// logged into the account. | ||||
| 			// cleanCount += | ||||
| 			// stmt.executeUpdate("DELETE FROM accounts WHERE accounts.login NOT IN (SELECT account_name FROM characters);"); | ||||
| 			 | ||||
| 			// Deletes banned accounts that have not been logged into for xx amount of days | ||||
| 			// (specified at the end of the script, default is set to 90 days). This prevents | ||||
| 			// accounts from being deleted that were accidentally or temporarily banned. | ||||
| 			// cleanCount += | ||||
| 			// stmt.executeUpdate("DELETE FROM accounts WHERE accounts.accessLevel < 0 AND DATEDIFF(CURRENT_DATE( ) , FROM_UNIXTIME(`lastactive`/1000)) > 90;"); | ||||
| 			// cleanCount += | ||||
| 			// stmt.executeUpdate("DELETE FROM characters WHERE characters.account_name NOT IN (SELECT login FROM accounts);"); | ||||
| 			 | ||||
| 			// If the character does not exist... | ||||
| 			// Characters | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM account_gsdata WHERE account_gsdata.account_name NOT IN (SELECT account_name FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_contacts WHERE character_contacts.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_contacts WHERE character_contacts.contactId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_friends WHERE character_friends.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_friends WHERE character_friends.friendId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_hennas WHERE character_hennas.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_macroses WHERE character_macroses.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_quests WHERE character_quests.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_recipebook WHERE character_recipebook.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_recipeshoplist WHERE character_recipeshoplist.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_shortcuts WHERE character_shortcuts.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_skills WHERE character_skills.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_skills_save WHERE character_skills_save.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_subclasses WHERE character_subclasses.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_instance_time WHERE character_instance_time.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			 | ||||
| 			// Items | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM items WHERE items.owner_id NOT IN (SELECT charId FROM characters) AND items.owner_id NOT IN (SELECT clan_id FROM clan_data) AND items.owner_id != -1;"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM items WHERE items.owner_id = -1 AND loc LIKE 'MAIL' AND loc_data NOT IN (SELECT messageId FROM messages WHERE senderId = -1);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM item_auction_bid WHERE item_auction_bid.playerObjId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM item_variations WHERE item_variations.itemId NOT IN (SELECT object_id FROM items);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM item_elementals WHERE item_elementals.itemId NOT IN (SELECT object_id FROM items);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM item_special_abilities WHERE item_special_abilities.objectId NOT IN (SELECT object_id FROM items);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM item_variables WHERE item_variables.id NOT IN (SELECT object_id FROM items);"); | ||||
| 			 | ||||
| 			// Misc | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM cursed_weapons WHERE cursed_weapons.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM heroes WHERE heroes.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM olympiad_nobles WHERE olympiad_nobles.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM olympiad_nobles_eom WHERE olympiad_nobles_eom.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM pets WHERE pets.item_obj_id NOT IN (SELECT object_id FROM items);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM merchant_lease WHERE merchant_lease.player_id NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_reco_bonus WHERE character_reco_bonus.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM clan_data WHERE clan_data.leader_id NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM clan_data WHERE clan_data.clan_id NOT IN (SELECT clanid FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM olympiad_fights WHERE olympiad_fights.charOneId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM olympiad_fights WHERE olympiad_fights.charTwoId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM heroes_diary WHERE heroes_diary.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_offline_trade WHERE character_offline_trade.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_offline_trade_items WHERE character_offline_trade_items.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_tpbookmark WHERE character_tpbookmark.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM character_variables WHERE character_variables.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM bot_reported_char_data WHERE bot_reported_char_data.botId NOT IN (SELECT charId FROM characters);"); | ||||
| 			 | ||||
| 			// If the clan does not exist... | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM clan_privs WHERE clan_privs.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM clan_skills WHERE clan_skills.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM clan_subpledges WHERE clan_subpledges.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM clan_wars WHERE clan_wars.clan1 NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM clan_wars WHERE clan_wars.clan2 NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM siege_clans WHERE siege_clans.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM clan_notices WHERE clan_notices.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM auction_bid WHERE auction_bid.bidderId NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			 | ||||
| 			// Forum Related | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM forums WHERE forums.forum_owner_id NOT IN (SELECT clan_id FROM clan_data) AND forums.forum_parent=2;"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM forums WHERE forums.forum_owner_id NOT IN (SELECT charId FROM characters) AND forums.forum_parent=3;"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM posts WHERE posts.post_forum_id NOT IN (SELECT forum_id FROM forums);"); | ||||
| 			cleanCount += stmt.executeUpdate("DELETE FROM topic WHERE topic.topic_forum_id NOT IN (SELECT forum_id FROM forums);"); | ||||
| 			 | ||||
| 			// Update needed items after cleaning has taken place. | ||||
| 			stmt.executeUpdate("UPDATE clan_data SET auction_bid_at = 0 WHERE auction_bid_at NOT IN (SELECT auctionId FROM auction_bid);"); | ||||
| 			stmt.executeUpdate("UPDATE clan_data SET new_leader_id = 0 WHERE new_leader_id <> 0 AND new_leader_id NOT IN (SELECT charId FROM characters);"); | ||||
| 			stmt.executeUpdate("UPDATE clan_subpledges SET leader_id=0 WHERE clan_subpledges.leader_id NOT IN (SELECT charId FROM characters) AND leader_id > 0;"); | ||||
| 			stmt.executeUpdate("UPDATE castle SET side='NEUTRAL' WHERE castle.id NOT IN (SELECT hasCastle FROM clan_data);"); | ||||
| 			stmt.executeUpdate("UPDATE characters SET clanid=0, clan_privs=0, wantspeace=0, subpledge=0, lvl_joined_academy=0, apprentice=0, sponsor=0, clan_join_expiry_time=0, clan_create_expiry_time=0 WHERE characters.clanid > 0 AND characters.clanid NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			stmt.executeUpdate("UPDATE fort SET owner=0 WHERE owner NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 			 | ||||
| 			LOGGER.info("Cleaned " + cleanCount + " elements from database in " + ((System.currentTimeMillis() - cleanupStart) / 1000) + " s"); | ||||
| 			try (Connection con = DatabaseFactory.getConnection(); | ||||
| 				Statement stmt = con.createStatement()) | ||||
| 			{ | ||||
| 				final long cleanupStart = System.currentTimeMillis(); | ||||
| 				int cleanCount = 0; | ||||
| 				 | ||||
| 				// Characters | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM account_gsdata WHERE account_gsdata.account_name NOT IN (SELECT account_name FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_contacts WHERE character_contacts.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_contacts WHERE character_contacts.contactId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_friends WHERE character_friends.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_friends WHERE character_friends.friendId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_hennas WHERE character_hennas.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_macroses WHERE character_macroses.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_quests WHERE character_quests.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_recipebook WHERE character_recipebook.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_recipeshoplist WHERE character_recipeshoplist.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_shortcuts WHERE character_shortcuts.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_skills WHERE character_skills.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_skills_save WHERE character_skills_save.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_subclasses WHERE character_subclasses.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_instance_time WHERE character_instance_time.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				 | ||||
| 				// Items | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM items WHERE items.owner_id NOT IN (SELECT charId FROM characters) AND items.owner_id NOT IN (SELECT clan_id FROM clan_data) AND items.owner_id != -1;"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM items WHERE items.owner_id = -1 AND loc LIKE 'MAIL' AND loc_data NOT IN (SELECT messageId FROM messages WHERE senderId = -1);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM item_auction_bid WHERE item_auction_bid.playerObjId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM item_variations WHERE item_variations.itemId NOT IN (SELECT object_id FROM items);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM item_elementals WHERE item_elementals.itemId NOT IN (SELECT object_id FROM items);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM item_special_abilities WHERE item_special_abilities.objectId NOT IN (SELECT object_id FROM items);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM item_variables WHERE item_variables.id NOT IN (SELECT object_id FROM items);"); | ||||
| 				 | ||||
| 				// Misc | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM cursed_weapons WHERE cursed_weapons.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM heroes WHERE heroes.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM olympiad_nobles WHERE olympiad_nobles.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM olympiad_nobles_eom WHERE olympiad_nobles_eom.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM pets WHERE pets.item_obj_id NOT IN (SELECT object_id FROM items);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM merchant_lease WHERE merchant_lease.player_id NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_reco_bonus WHERE character_reco_bonus.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM clan_data WHERE clan_data.leader_id NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM clan_data WHERE clan_data.clan_id NOT IN (SELECT clanid FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM olympiad_fights WHERE olympiad_fights.charOneId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM olympiad_fights WHERE olympiad_fights.charTwoId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM heroes_diary WHERE heroes_diary.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_offline_trade WHERE character_offline_trade.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_offline_trade_items WHERE character_offline_trade_items.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_tpbookmark WHERE character_tpbookmark.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM character_variables WHERE character_variables.charId NOT IN (SELECT charId FROM characters);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM bot_reported_char_data WHERE bot_reported_char_data.botId NOT IN (SELECT charId FROM characters);"); | ||||
| 				 | ||||
| 				// Clan | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM clan_privs WHERE clan_privs.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM clan_skills WHERE clan_skills.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM clan_subpledges WHERE clan_subpledges.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM clan_wars WHERE clan_wars.clan1 NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM clan_wars WHERE clan_wars.clan2 NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM siege_clans WHERE siege_clans.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM clan_notices WHERE clan_notices.clan_id NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM auction_bid WHERE auction_bid.bidderId NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				 | ||||
| 				// Forums | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM forums WHERE forums.forum_owner_id NOT IN (SELECT clan_id FROM clan_data) AND forums.forum_parent=2;"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM forums WHERE forums.forum_owner_id NOT IN (SELECT charId FROM characters) AND forums.forum_parent=3;"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM posts WHERE posts.post_forum_id NOT IN (SELECT forum_id FROM forums);"); | ||||
| 				cleanCount += stmt.executeUpdate("DELETE FROM topic WHERE topic.topic_forum_id NOT IN (SELECT forum_id FROM forums);"); | ||||
| 				 | ||||
| 				// Update needed items after cleaning has taken place. | ||||
| 				stmt.executeUpdate("UPDATE clan_data SET auction_bid_at = 0 WHERE auction_bid_at NOT IN (SELECT auctionId FROM auction_bid);"); | ||||
| 				stmt.executeUpdate("UPDATE clan_data SET new_leader_id = 0 WHERE new_leader_id <> 0 AND new_leader_id NOT IN (SELECT charId FROM characters);"); | ||||
| 				stmt.executeUpdate("UPDATE clan_subpledges SET leader_id=0 WHERE clan_subpledges.leader_id NOT IN (SELECT charId FROM characters) AND leader_id > 0;"); | ||||
| 				stmt.executeUpdate("UPDATE castle SET side='NEUTRAL' WHERE castle.id NOT IN (SELECT hasCastle FROM clan_data);"); | ||||
| 				stmt.executeUpdate("UPDATE characters SET clanid=0, clan_privs=0, wantspeace=0, subpledge=0, lvl_joined_academy=0, apprentice=0, sponsor=0, clan_join_expiry_time=0, clan_create_expiry_time=0 WHERE characters.clanid > 0 AND characters.clanid NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				stmt.executeUpdate("UPDATE fort SET owner=0 WHERE owner NOT IN (SELECT clan_id FROM clan_data);"); | ||||
| 				 | ||||
| 				LOGGER.info("IdFactory: Cleaned " + cleanCount + " elements from database in " + ((System.currentTimeMillis() - cleanupStart) / 1000) + " seconds."); | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				LOGGER.warning("IdFactory: Could not clean up database: " + e); | ||||
| 			} | ||||
| 		} | ||||
| 		catch (SQLException e) | ||||
| 		{ | ||||
| 			LOGGER.log(Level.WARNING, "Could not clean up database: " + e.getMessage(), e); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	private void cleanUpTimeStamps() | ||||
| 	{ | ||||
| 		 | ||||
| 		// Cleanup timestamps. | ||||
| 		try (Connection con = DatabaseFactory.getConnection()) | ||||
| 		{ | ||||
| 			int cleanCount = 0; | ||||
| @@ -263,62 +202,136 @@ public abstract class IdFactory | ||||
| 					cleanCount += stmt.executeUpdate(); | ||||
| 				} | ||||
| 			} | ||||
| 			LOGGER.info("Cleaned " + cleanCount + " expired timestamps from database."); | ||||
| 			LOGGER.info("IdFactory: Cleaned " + cleanCount + " expired timestamps from database."); | ||||
| 		} | ||||
| 		catch (SQLException e) | ||||
| 		catch (Exception e) | ||||
| 		{ | ||||
| 			// Ignore. | ||||
| 			LOGGER.warning("IdFactory: Could not clean expired timestamps from database. " + e); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * @return | ||||
| 	 * @throws Exception | ||||
| 	 * @throws SQLException | ||||
| 	 */ | ||||
| 	protected final Integer[] extractUsedObjectIDTable() throws Exception | ||||
| 	{ | ||||
| 		final List<Integer> temp = new ArrayList<>(); | ||||
| 		try (Connection con = DatabaseFactory.getConnection(); | ||||
| 			Statement s = con.createStatement()) | ||||
| 		 | ||||
| 		// Initialize. | ||||
| 		try | ||||
| 		{ | ||||
| 			String extractUsedObjectIdsQuery = ""; | ||||
| 			_freeIds = new BitSet(PrimeFinder.nextPrime(100000)); | ||||
| 			_freeIds.clear(); | ||||
| 			_freeIdCount = new AtomicInteger(FREE_OBJECT_ID_SIZE); | ||||
| 			 | ||||
| 			for (String[] tblClmn : ID_EXTRACTS) | ||||
| 			// Collect already used ids. | ||||
| 			final List<Integer> usedIds = new ArrayList<>(); | ||||
| 			try (Connection con = DatabaseFactory.getConnection(); | ||||
| 				Statement statement = con.createStatement()) | ||||
| 			{ | ||||
| 				extractUsedObjectIdsQuery += "SELECT " + tblClmn[1] + " FROM " + tblClmn[0] + " UNION "; | ||||
| 			} | ||||
| 			 | ||||
| 			extractUsedObjectIdsQuery = extractUsedObjectIdsQuery.substring(0, extractUsedObjectIdsQuery.length() - 7); // Remove the last " UNION " | ||||
| 			try (ResultSet rs = s.executeQuery(extractUsedObjectIdsQuery)) | ||||
| 			{ | ||||
| 				while (rs.next()) | ||||
| 				String extractUsedObjectIdsQuery = ""; | ||||
| 				for (String[] tblClmn : ID_EXTRACTS) | ||||
| 				{ | ||||
| 					temp.add(rs.getInt(1)); | ||||
| 					extractUsedObjectIdsQuery += "SELECT " + tblClmn[1] + " FROM " + tblClmn[0] + " UNION "; | ||||
| 				} | ||||
| 				extractUsedObjectIdsQuery = extractUsedObjectIdsQuery.substring(0, extractUsedObjectIdsQuery.length() - 7); // Remove the last " UNION " | ||||
| 				try (ResultSet result = statement.executeQuery(extractUsedObjectIdsQuery)) | ||||
| 				{ | ||||
| 					while (result.next()) | ||||
| 					{ | ||||
| 						usedIds.add(result.getInt(1)); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			Collections.sort(usedIds); | ||||
| 			 | ||||
| 			// Register used ids. | ||||
| 			for (int usedObjectId : usedIds) | ||||
| 			{ | ||||
| 				final int objectId = usedObjectId - FIRST_OID; | ||||
| 				if (objectId < 0) | ||||
| 				{ | ||||
| 					LOGGER.warning("IdFactory: Object ID " + usedObjectId + " in DB is less than minimum ID of " + FIRST_OID); | ||||
| 					continue; | ||||
| 				} | ||||
| 				_freeIds.set(usedObjectId - FIRST_OID); | ||||
| 				_freeIdCount.decrementAndGet(); | ||||
| 			} | ||||
| 			 | ||||
| 			_nextFreeId = new AtomicInteger(_freeIds.nextClearBit(0)); | ||||
| 			_initialized = true; | ||||
| 		} | ||||
| 		Collections.sort(temp); | ||||
| 		return temp.toArray(new Integer[temp.size()]); | ||||
| 		catch (Exception e) | ||||
| 		{ | ||||
| 			_initialized = false; | ||||
| 			LOGGER.severe("IdFactory: Could not be initialized properly: " + e.getMessage()); | ||||
| 		} | ||||
| 		 | ||||
| 		// Schedule increase capacity task. | ||||
| 		ThreadPool.scheduleAtFixedRate(() -> | ||||
| 		{ | ||||
| 			synchronized (_nextFreeId) | ||||
| 			{ | ||||
| 				if (PrimeFinder.nextPrime((usedIdCount() * 11) / 10) > _freeIds.size()) | ||||
| 				{ | ||||
| 					increaseBitSetCapacity(); | ||||
| 				} | ||||
| 			} | ||||
| 		}, 30000, 30000); | ||||
| 		 | ||||
| 		LOGGER.info("IdFactory: " + _freeIds.size() + " id's available."); | ||||
| 	} | ||||
| 	 | ||||
| 	public boolean isInitialized() | ||||
| 	public synchronized static void releaseId(int objectId) | ||||
| 	{ | ||||
| 		synchronized (_nextFreeId) | ||||
| 		{ | ||||
| 			if ((objectId - FIRST_OID) > -1) | ||||
| 			{ | ||||
| 				_freeIds.clear(objectId - FIRST_OID); | ||||
| 				_freeIdCount.incrementAndGet(); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				LOGGER.warning("IdFactory: Release objectID " + objectId + " failed (< " + FIRST_OID + ")"); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	public synchronized static int getNextId() | ||||
| 	{ | ||||
| 		synchronized (_nextFreeId) | ||||
| 		{ | ||||
| 			final int newId = _nextFreeId.get(); | ||||
| 			_freeIds.set(newId); | ||||
| 			_freeIdCount.decrementAndGet(); | ||||
| 			 | ||||
| 			final int nextFree = _freeIds.nextClearBit(newId) < 0 ? _freeIds.nextClearBit(0) : _freeIds.nextClearBit(newId); | ||||
| 			if (nextFree < 0) | ||||
| 			{ | ||||
| 				if (_freeIds.size() >= FREE_OBJECT_ID_SIZE) | ||||
| 				{ | ||||
| 					throw new NullPointerException("IdFactory: Ran out of valid ids."); | ||||
| 				} | ||||
| 				increaseBitSetCapacity(); | ||||
| 			} | ||||
| 			_nextFreeId.set(nextFree); | ||||
| 			 | ||||
| 			return newId + FIRST_OID; | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	private static void increaseBitSetCapacity() | ||||
| 	{ | ||||
| 		final BitSet newBitSet = new BitSet(PrimeFinder.nextPrime((usedIdCount() * 11) / 10)); | ||||
| 		newBitSet.or(_freeIds); | ||||
| 		_freeIds = newBitSet; | ||||
| 	} | ||||
| 	 | ||||
| 	private static int usedIdCount() | ||||
| 	{ | ||||
| 		return _freeIdCount.get() - FIRST_OID; | ||||
| 	} | ||||
| 	 | ||||
| 	public static int size() | ||||
| 	{ | ||||
| 		return _freeIdCount.get(); | ||||
| 	} | ||||
| 	 | ||||
| 	public static boolean hasInitialized() | ||||
| 	{ | ||||
| 		return _initialized; | ||||
| 	} | ||||
| 	 | ||||
| 	public static IdFactory getInstance() | ||||
| 	{ | ||||
| 		return _instance; | ||||
| 	} | ||||
| 	 | ||||
| 	public abstract int getNextId(); | ||||
| 	 | ||||
| 	/** | ||||
| 	 * return a used Object ID back to the pool | ||||
| 	 * @param id | ||||
| 	 */ | ||||
| 	public abstract void releaseId(int id); | ||||
| 	 | ||||
| 	public abstract int size(); | ||||
| } | ||||
|   | ||||
| @@ -1,152 +0,0 @@ | ||||
| /* | ||||
|  * This file is part of the L2J Mobius project. | ||||
|  *  | ||||
|  * 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 org.l2jmobius.gameserver.idfactory; | ||||
|  | ||||
| import java.sql.Connection; | ||||
| import java.sql.PreparedStatement; | ||||
| import java.sql.ResultSet; | ||||
| import java.sql.SQLException; | ||||
| import java.util.Stack; | ||||
|  | ||||
| import org.l2jmobius.Config; | ||||
| import org.l2jmobius.commons.database.DatabaseFactory; | ||||
|  | ||||
| /** | ||||
|  * @version $Revision: 1.3.2.1.2.7 $ $Date: 2005/04/11 10:06:12 $ | ||||
|  */ | ||||
| public class StackIDFactory extends IdFactory | ||||
| { | ||||
| 	private int _curOID; | ||||
| 	private int _tempOID; | ||||
| 	 | ||||
| 	private final Stack<Integer> _freeOIDStack = new Stack<>(); | ||||
| 	 | ||||
| 	protected StackIDFactory() | ||||
| 	{ | ||||
| 		super(); | ||||
| 		_curOID = FIRST_OID; | ||||
| 		_tempOID = FIRST_OID; | ||||
| 		 | ||||
| 		try (Connection con = DatabaseFactory.getConnection()) | ||||
| 		{ | ||||
| 			// con.createStatement().execute("drop table if exists tmp_obj_id"); | ||||
| 			 | ||||
| 			final Integer[] tmpObjIds = extractUsedObjectIDTable(); | ||||
| 			if (tmpObjIds.length > 0) | ||||
| 			{ | ||||
| 				_curOID = tmpObjIds[tmpObjIds.length - 1]; | ||||
| 			} | ||||
| 			LOGGER.info("Max Id = " + _curOID); | ||||
| 			 | ||||
| 			int n = tmpObjIds.length; | ||||
| 			for (int idx = 0; idx < n; idx++) | ||||
| 			{ | ||||
| 				n = insertUntil(tmpObjIds, idx, n, con); | ||||
| 			} | ||||
| 			 | ||||
| 			_curOID++; | ||||
| 			LOGGER.info("IdFactory: Next usable Object ID is: " + _curOID); | ||||
| 			_initialized = true; | ||||
| 		} | ||||
| 		catch (Exception e) | ||||
| 		{ | ||||
| 			LOGGER.severe(getClass().getSimpleName() + ": Could not be initialized properly:" + e.getMessage()); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	private int insertUntil(Integer[] tmpObjIds, int idx, int n, Connection con) throws SQLException | ||||
| 	{ | ||||
| 		final int id = tmpObjIds[idx]; | ||||
| 		if (id == _tempOID) | ||||
| 		{ | ||||
| 			_tempOID++; | ||||
| 			return n; | ||||
| 		} | ||||
| 		// check these IDs not present in DB | ||||
| 		if (Config.BAD_ID_CHECKING) | ||||
| 		{ | ||||
| 			for (String check : ID_CHECKS) | ||||
| 			{ | ||||
| 				try (PreparedStatement ps = con.prepareStatement(check)) | ||||
| 				{ | ||||
| 					ps.setInt(1, _tempOID); | ||||
| 					// ps.setInt(1, _curOID); | ||||
| 					ps.setInt(2, id); | ||||
| 					try (ResultSet rs = ps.executeQuery()) | ||||
| 					{ | ||||
| 						if (rs.next()) | ||||
| 						{ | ||||
| 							final int badId = rs.getInt(1); | ||||
| 							LOGGER.severe("Bad ID " + badId + " in DB found by: " + check); | ||||
| 							throw new RuntimeException(); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		// int hole = id - _curOID; | ||||
| 		final int hole = (id - _tempOID) > (n - idx) ? n - idx : id - _tempOID; | ||||
| 		for (int i = 1; i <= hole; i++) | ||||
| 		{ | ||||
| 			_freeOIDStack.push(_tempOID); | ||||
| 			_tempOID++; | ||||
| 		} | ||||
| 		if (hole < (n - idx)) | ||||
| 		{ | ||||
| 			_tempOID++; | ||||
| 		} | ||||
| 		return n - hole; | ||||
| 	} | ||||
| 	 | ||||
| 	public static IdFactory getInstance() | ||||
| 	{ | ||||
| 		return _instance; | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public synchronized int getNextId() | ||||
| 	{ | ||||
| 		int id; | ||||
| 		if (!_freeOIDStack.empty()) | ||||
| 		{ | ||||
| 			id = _freeOIDStack.pop(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			id = _curOID; | ||||
| 			_curOID += 1; | ||||
| 		} | ||||
| 		return id; | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
| 	 * return a used Object ID back to the pool | ||||
| 	 * @param id | ||||
| 	 */ | ||||
| 	@Override | ||||
| 	public synchronized void releaseId(int id) | ||||
| 	{ | ||||
| 		_freeOIDStack.push(id); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public int size() | ||||
| 	{ | ||||
| 		return (FREE_OBJECT_ID_SIZE - _curOID) + FIRST_OID + _freeOIDStack.size(); | ||||
| 	} | ||||
| } | ||||
| @@ -123,7 +123,7 @@ public class AirShipManager | ||||
| 		if (_airShips.containsKey(ownerId)) | ||||
| 		{ | ||||
| 			airShip = _airShips.get(ownerId); | ||||
| 			airShip.refreshID(); | ||||
| 			airShip.refreshId(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
|   | ||||
| @@ -282,7 +282,7 @@ public class MailManager | ||||
| 		} | ||||
| 		 | ||||
| 		_messages.remove(msgId); | ||||
| 		IdFactory.getInstance().releaseId(msgId); | ||||
| 		IdFactory.releaseId(msgId); | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
|   | ||||
| @@ -47,7 +47,7 @@ public class Petition | ||||
| 	 | ||||
| 	public Petition(PlayerInstance petitioner, String petitionText, int petitionType) | ||||
| 	{ | ||||
| 		_id = IdFactory.getInstance().getNextId(); | ||||
| 		_id = IdFactory.getNextId(); | ||||
| 		_type = PetitionType.values()[--petitionType]; | ||||
| 		_content = petitionText; | ||||
| 		_petitioner = petitioner; | ||||
|   | ||||
| @@ -532,7 +532,7 @@ public class Spawn extends Location implements IIdentifiable, INamable | ||||
| 	{ | ||||
| 		if (_doRespawn) | ||||
| 		{ | ||||
| 			oldNpc.refreshID(); | ||||
| 			// oldNpc.refreshID(); | ||||
| 			initializeNpcInstance(oldNpc); | ||||
| 			 | ||||
| 			// Register NPC back to instance world. | ||||
|   | ||||
| @@ -151,11 +151,11 @@ public abstract class WorldObject extends ListenersContainer implements IIdentif | ||||
| 		return true; | ||||
| 	} | ||||
| 	 | ||||
| 	public void refreshID() | ||||
| 	public void refreshId() | ||||
| 	{ | ||||
| 		World.getInstance().removeObject(this); | ||||
| 		IdFactory.getInstance().releaseId(getObjectId()); | ||||
| 		_objectId = IdFactory.getInstance().getNextId(); | ||||
| 		IdFactory.releaseId(getObjectId()); | ||||
| 		_objectId = IdFactory.getNextId(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
|   | ||||
| @@ -290,7 +290,7 @@ public abstract class Creature extends WorldObject implements ISkillsHolder, IDe | ||||
| 	 */ | ||||
| 	public Creature(CreatureTemplate template) | ||||
| 	{ | ||||
| 		this(IdFactory.getInstance().getNextId(), template); | ||||
| 		this(IdFactory.getNextId(), template); | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
|   | ||||
| @@ -48,7 +48,7 @@ public class ControllableAirShipInstance extends AirShipInstance | ||||
| 		super(template); | ||||
| 		setInstanceType(InstanceType.ControllableAirShipInstance); | ||||
| 		_ownerId = ownerId; | ||||
| 		_helmId = IdFactory.getInstance().getNextId(); // not forget to release ! | ||||
| 		_helmId = IdFactory.getNextId(); // not forget to release ! | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| @@ -281,11 +281,11 @@ public class ControllableAirShipInstance extends AirShipInstance | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void refreshID() | ||||
| 	public void refreshId() | ||||
| 	{ | ||||
| 		super.refreshID(); | ||||
| 		IdFactory.getInstance().releaseId(_helmId); | ||||
| 		_helmId = IdFactory.getInstance().getNextId(); | ||||
| 		super.refreshId(); | ||||
| 		IdFactory.releaseId(_helmId); | ||||
| 		_helmId = IdFactory.getNextId(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
|   | ||||
| @@ -44,7 +44,7 @@ public class FenceInstance extends WorldObject | ||||
| 	 | ||||
| 	public FenceInstance(int x, int y, String name, int width, int length, int height, FenceState state) | ||||
| 	{ | ||||
| 		super(IdFactory.getInstance().getNextId()); | ||||
| 		super(IdFactory.getNextId()); | ||||
| 		 | ||||
| 		_xMin = x - (width / 2); | ||||
| 		_xMax = x + (width / 2); | ||||
| @@ -62,7 +62,7 @@ public class FenceInstance extends WorldObject | ||||
| 			_heightFences = new int[height - 1]; | ||||
| 			for (int i = 0; i < _heightFences.length; i++) | ||||
| 			{ | ||||
| 				_heightFences[i] = IdFactory.getInstance().getNextId(); | ||||
| 				_heightFences[i] = IdFactory.getNextId(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -1141,7 +1141,7 @@ public class PlayerInstance extends Playable | ||||
| 	 */ | ||||
| 	private PlayerInstance(PlayerTemplate template, String accountName, PlayerAppearance app) | ||||
| 	{ | ||||
| 		this(IdFactory.getInstance().getNextId(), template, accountName, app); | ||||
| 		this(IdFactory.getNextId(), template, accountName, app); | ||||
| 	} | ||||
| 	 | ||||
| 	@Override | ||||
|   | ||||
| @@ -164,7 +164,7 @@ public class RaceManagerInstance extends Npc | ||||
| 				player.setRace(0, 0); | ||||
| 				player.setRace(1, 0); | ||||
| 				 | ||||
| 				ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), 4443); | ||||
| 				ItemInstance item = new ItemInstance(IdFactory.getNextId(), 4443); | ||||
| 				item.setCount(1); | ||||
| 				item.setEnchantLevel(MonsterRace.getInstance().getRaceNumber()); | ||||
| 				item.setCustomType1(ticket); | ||||
|   | ||||
| @@ -32,7 +32,7 @@ public class EventAnnouncement implements IAnnouncement | ||||
| 	 | ||||
| 	public EventAnnouncement(DateRange range, String content) | ||||
| 	{ | ||||
| 		_id = IdFactory.getInstance().getNextId(); | ||||
| 		_id = IdFactory.getNextId(); | ||||
| 		_range = range; | ||||
| 		_content = content; | ||||
| 	} | ||||
| @@ -88,7 +88,7 @@ public class EventAnnouncement implements IAnnouncement | ||||
| 	@Override | ||||
| 	public boolean deleteMe() | ||||
| 	{ | ||||
| 		IdFactory.getInstance().releaseId(_id); | ||||
| 		IdFactory.releaseId(_id); | ||||
| 		return true; | ||||
| 	} | ||||
| 	 | ||||
|   | ||||
| @@ -34,7 +34,7 @@ public class Couple | ||||
| { | ||||
| 	private static final Logger LOGGER = Logger.getLogger(Couple.class.getName()); | ||||
| 	 | ||||
| 	private int _Id = 0; | ||||
| 	private int _id = 0; | ||||
| 	private int _player1Id = 0; | ||||
| 	private int _player2Id = 0; | ||||
| 	private boolean _maried = false; | ||||
| @@ -43,12 +43,12 @@ public class Couple | ||||
| 	 | ||||
| 	public Couple(int coupleId) | ||||
| 	{ | ||||
| 		_Id = coupleId; | ||||
| 		_id = coupleId; | ||||
| 		 | ||||
| 		try (Connection con = DatabaseFactory.getConnection(); | ||||
| 			PreparedStatement ps = con.prepareStatement("SELECT * FROM mods_wedding WHERE id = ?")) | ||||
| 		{ | ||||
| 			ps.setInt(1, _Id); | ||||
| 			ps.setInt(1, _id); | ||||
| 			try (ResultSet rs = ps.executeQuery()) | ||||
| 			{ | ||||
| 				while (rs.next()) | ||||
| @@ -88,8 +88,8 @@ public class Couple | ||||
| 		try (Connection con = DatabaseFactory.getConnection(); | ||||
| 			PreparedStatement ps = con.prepareStatement("INSERT INTO mods_wedding (id, player1Id, player2Id, married, affianceDate, weddingDate) VALUES (?, ?, ?, ?, ?, ?)")) | ||||
| 		{ | ||||
| 			_Id = IdFactory.getInstance().getNextId(); | ||||
| 			ps.setInt(1, _Id); | ||||
| 			_id = IdFactory.getNextId(); | ||||
| 			ps.setInt(1, _id); | ||||
| 			ps.setInt(2, _player1Id); | ||||
| 			ps.setInt(3, _player2Id); | ||||
| 			ps.setBoolean(4, false); | ||||
| @@ -111,7 +111,7 @@ public class Couple | ||||
| 			ps.setBoolean(1, true); | ||||
| 			_weddingDate = Calendar.getInstance(); | ||||
| 			ps.setLong(2, _weddingDate.getTimeInMillis()); | ||||
| 			ps.setInt(3, _Id); | ||||
| 			ps.setInt(3, _id); | ||||
| 			ps.execute(); | ||||
| 			_maried = true; | ||||
| 		} | ||||
| @@ -126,7 +126,7 @@ public class Couple | ||||
| 		try (Connection con = DatabaseFactory.getConnection(); | ||||
| 			PreparedStatement ps = con.prepareStatement("DELETE FROM mods_wedding WHERE id=?")) | ||||
| 		{ | ||||
| 			ps.setInt(1, _Id); | ||||
| 			ps.setInt(1, _id); | ||||
| 			ps.execute(); | ||||
| 		} | ||||
| 		catch (Exception e) | ||||
| @@ -137,7 +137,7 @@ public class Couple | ||||
| 	 | ||||
| 	public int getId() | ||||
| 	{ | ||||
| 		return _Id; | ||||
| 		return _id; | ||||
| 	} | ||||
| 	 | ||||
| 	public int getPlayer1Id() | ||||
|   | ||||
| @@ -99,7 +99,7 @@ public class Message | ||||
| 	 */ | ||||
| 	public Message(int senderId, int receiverId, boolean isCod, String subject, String text, long reqAdena) | ||||
| 	{ | ||||
| 		_messageId = IdFactory.getInstance().getNextId(); | ||||
| 		_messageId = IdFactory.getNextId(); | ||||
| 		_senderId = senderId; | ||||
| 		_receiverId = receiverId; | ||||
| 		_subject = subject; | ||||
| @@ -118,7 +118,7 @@ public class Message | ||||
| 	 */ | ||||
| 	public Message(int receiverId, String subject, String content, MailType sendBySystem) | ||||
| 	{ | ||||
| 		_messageId = IdFactory.getInstance().getNextId(); | ||||
| 		_messageId = IdFactory.getNextId(); | ||||
| 		_senderId = -1; | ||||
| 		_receiverId = receiverId; | ||||
| 		_subject = subject; | ||||
| @@ -138,7 +138,7 @@ public class Message | ||||
| 	 */ | ||||
| 	public Message(int senderId, int receiverId, String subject, String content, MailType sendBySystem) | ||||
| 	{ | ||||
| 		_messageId = IdFactory.getInstance().getNextId(); | ||||
| 		_messageId = IdFactory.getNextId(); | ||||
| 		_senderId = senderId; | ||||
| 		_receiverId = receiverId; | ||||
| 		_subject = subject; | ||||
| @@ -157,7 +157,7 @@ public class Message | ||||
| 	 */ | ||||
| 	public Message(Message msg) | ||||
| 	{ | ||||
| 		_messageId = IdFactory.getInstance().getNextId(); | ||||
| 		_messageId = IdFactory.getNextId(); | ||||
| 		_senderId = msg.getSenderId(); | ||||
| 		_receiverId = msg.getSenderId(); | ||||
| 		_subject = ""; | ||||
| @@ -177,7 +177,7 @@ public class Message | ||||
| 	 | ||||
| 	public Message(int receiverId, ItemInstance item, MailType mailType) | ||||
| 	{ | ||||
| 		_messageId = IdFactory.getInstance().getNextId(); | ||||
| 		_messageId = IdFactory.getNextId(); | ||||
| 		_senderId = -1; | ||||
| 		_receiverId = receiverId; | ||||
| 		_subject = ""; | ||||
|   | ||||
| @@ -79,7 +79,7 @@ public class AuctionItem | ||||
| 	 | ||||
| 	public ItemInstance createNewItemInstance() | ||||
| 	{ | ||||
| 		final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), _itemId); | ||||
| 		final ItemInstance item = new ItemInstance(IdFactory.getNextId(), _itemId); | ||||
| 		World.getInstance().addObject(item); | ||||
| 		item.setCount(_itemCount); | ||||
| 		item.setEnchantLevel(item.getItem().getDefaultEnchantLevel()); | ||||
|   | ||||
| @@ -259,7 +259,7 @@ public class ItemInstance extends WorldObject | ||||
| 	 */ | ||||
| 	public ItemInstance(int itemId) | ||||
| 	{ | ||||
| 		this(IdFactory.getInstance().getNextId(), itemId); | ||||
| 		this(IdFactory.getNextId(), itemId); | ||||
| 	} | ||||
| 	 | ||||
| 	/** | ||||
|   | ||||
| @@ -50,7 +50,7 @@ public abstract class ZoneForm | ||||
| 	 | ||||
| 	protected final void dropDebugItem(int itemId, int num, int x, int y, int z) | ||||
| 	{ | ||||
| 		final ItemInstance item = new ItemInstance(IdFactory.getInstance().getNextId(), itemId); | ||||
| 		final ItemInstance item = new ItemInstance(IdFactory.getNextId(), itemId); | ||||
| 		item.setCount(num); | ||||
| 		item.spawnMe(x, y, z + 5); | ||||
| 		ZoneManager.getInstance().getDebugItems().add(item); | ||||
|   | ||||
| @@ -248,7 +248,7 @@ public class MinionList | ||||
| 			// minion can be already spawned or deleted | ||||
| 			if (!_master.isAlikeDead() && _master.isSpawned() && !_minion.isSpawned()) | ||||
| 			{ | ||||
| 				_minion.refreshID(); | ||||
| 				// _minion.refreshId(); | ||||
| 				initializeNpcInstance(_master, _minion); | ||||
| 				 | ||||
| 				// assist master | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 MobiusDevelopment
					MobiusDevelopment