diff --git a/lang/en.yml b/lang/en.yml index 03a4415..0be57b5 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -209,4 +209,4 @@ VALID_DONATION_KEY: "Valid donation key." VERSION_INCOMPATIBLE: "{0} {1} is not supported." VERSION_NOTICE: "Version {0} is now available." VERSION_REQUIRED: "{0} {1} or higher is required." -WORLD_NOT_FOUND: "World \"{0}\" not found." +WORLD_NOT_FOUND: "World \"{0}\" not found." \ No newline at end of file diff --git a/lang/ru.yml b/lang/ru.yml index a82438b..bee1016 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -139,7 +139,7 @@ PATCH_SKIP_UPDATE: "{Пропущена таблица|Пропущен инде PATCH_STARTED: "Выполняется обновление {0}. Пожалуйста, подождите..." PATCH_SUCCESS: "Успешно обновлен до {0}." PATCH_UPGRADING: "Выполняется обновление базы данных. Пожалуйста, подождите..." -PLEASE_SELECT: "Пожалуйста выберете: «{0}» или «{1}»." +PLEASE_SELECT: "Пожалуйста выберите: «{0}» или «{1}»." PREVIEW_CANCELLED: "Предварительный просмотр отменен." PREVIEW_CANCELLING: "Отмена предварительного просмотра..." PREVIEW_IN_GAME: "Предварительный просмотр откатов доступен только в игре." diff --git a/pom.xml b/pom.xml index ffaf6ec..8ad0cbe 100755 --- a/pom.xml +++ b/pom.xml @@ -113,6 +113,31 @@ ${skipTests} + + org.apache.maven.plugins + maven-enforcer-plugin + 3.6.1 + + + validate + enforce + + + + + central + enginehub-repo + spigot-repo + paper-repo + codemc-repo + jitpack.io + + + + + + + diff --git a/src/main/java/net/coreprotect/bukkit/BukkitAdapter.java b/src/main/java/net/coreprotect/bukkit/BukkitAdapter.java index f498ef3..23f9207 100644 --- a/src/main/java/net/coreprotect/bukkit/BukkitAdapter.java +++ b/src/main/java/net/coreprotect/bukkit/BukkitAdapter.java @@ -1,7 +1,10 @@ package net.coreprotect.bukkit; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.bukkit.Color; import org.bukkit.DyeColor; @@ -20,6 +23,7 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.LivingEntity; import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.MerchantRecipe; @@ -85,6 +89,7 @@ public class BukkitAdapter implements BukkitInterface { } // -------------------- Basic data conversion methods -------------------- + public static Set EMPTY_SET = new HashSet<>(Arrays.asList()); @Override public String parseLegacyName(String name) { @@ -350,4 +355,19 @@ public class BukkitAdapter implements BukkitInterface { public Object getRegistryValue(String key, Object tClass) { return null; } + + @Override + public boolean isCrafter(InventoryType type) { + return false; + } + + @Override + public boolean isCopperChest(Material material) { + return false; + } + + @Override + public Set copperChestMaterials() { + return EMPTY_SET; + } } diff --git a/src/main/java/net/coreprotect/bukkit/BukkitInterface.java b/src/main/java/net/coreprotect/bukkit/BukkitInterface.java index a648b26..c66d7fc 100644 --- a/src/main/java/net/coreprotect/bukkit/BukkitInterface.java +++ b/src/main/java/net/coreprotect/bukkit/BukkitInterface.java @@ -2,6 +2,7 @@ package net.coreprotect.bukkit; import java.util.List; import java.util.Map; +import java.util.Set; import org.bukkit.Material; import org.bukkit.World; @@ -14,6 +15,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.MerchantRecipe; @@ -432,4 +434,11 @@ public interface BukkitInterface { * @return The parsed name */ String parseLegacyName(String name); + + boolean isCrafter(InventoryType type); + + boolean isCopperChest(Material material); + + Set copperChestMaterials(); + } diff --git a/src/main/java/net/coreprotect/bukkit/Bukkit_v1_21.java b/src/main/java/net/coreprotect/bukkit/Bukkit_v1_21.java index 6cea78e..4d52be8 100644 --- a/src/main/java/net/coreprotect/bukkit/Bukkit_v1_21.java +++ b/src/main/java/net/coreprotect/bukkit/Bukkit_v1_21.java @@ -1,5 +1,7 @@ package net.coreprotect.bukkit; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -9,6 +11,7 @@ import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Tag; import org.bukkit.entity.EntityType; +import org.bukkit.event.inventory.InventoryType; import net.coreprotect.model.BlockGroup; @@ -22,6 +25,8 @@ import net.coreprotect.model.BlockGroup; */ public class Bukkit_v1_21 extends Bukkit_v1_20 implements BukkitInterface { + public static Set COPPER_CHESTS = new HashSet<>(Arrays.asList()); + /** * Initializes the Bukkit_v1_21 adapter with 1.21-specific block groups and mappings. * Sets up collections of blocks with similar behavior for efficient handling. @@ -29,6 +34,9 @@ public class Bukkit_v1_21 extends Bukkit_v1_20 implements BukkitInterface { public Bukkit_v1_21() { initializeBlockGroups(); initializeTrapdoorBlocks(); + BlockGroup.INTERACT_BLOCKS.addAll(copperChestMaterials()); + BlockGroup.CONTAINERS.addAll(copperChestMaterials()); + BlockGroup.UPDATE_STATE.addAll(copperChestMaterials()); } /** @@ -154,4 +162,40 @@ public class Bukkit_v1_21 extends Bukkit_v1_20 implements BukkitInterface { wolf.setVariant(variant); } + + @Override + public boolean isCrafter(InventoryType type) { + return type == InventoryType.CRAFTER; + } + + @Override + public boolean isCopperChest(Material material) { + if (COPPER_CHESTS.contains(material) && material != Material.CHEST) { + return true; + } + + return false; + } + + @Override + public Set copperChestMaterials() { + if (COPPER_CHESTS.isEmpty()) { + Material copperChest = Material.getMaterial("COPPER_CHEST"); + if (copperChest == null) { + COPPER_CHESTS.add(Material.CHEST); + } + else { + COPPER_CHESTS.add(Material.getMaterial("COPPER_CHEST")); + COPPER_CHESTS.add(Material.getMaterial("EXPOSED_COPPER_CHEST")); + COPPER_CHESTS.add(Material.getMaterial("WEATHERED_COPPER_CHEST")); + COPPER_CHESTS.add(Material.getMaterial("OXIDIZED_COPPER_CHEST")); + COPPER_CHESTS.add(Material.getMaterial("WAXED_COPPER_CHEST")); + COPPER_CHESTS.add(Material.getMaterial("WAXED_EXPOSED_COPPER_CHEST")); + COPPER_CHESTS.add(Material.getMaterial("WAXED_WEATHERED_COPPER_CHEST")); + COPPER_CHESTS.add(Material.getMaterial("WAXED_OXIDIZED_COPPER_CHEST")); + } + } + + return COPPER_CHESTS; + } } diff --git a/src/main/java/net/coreprotect/command/StatusCommand.java b/src/main/java/net/coreprotect/command/StatusCommand.java index 040061f..f035f77 100755 --- a/src/main/java/net/coreprotect/command/StatusCommand.java +++ b/src/main/java/net/coreprotect/command/StatusCommand.java @@ -39,9 +39,13 @@ public class StatusCommand { String versionCheck = ""; if (Config.getGlobal().CHECK_UPDATES) { String latestVersion = NetworkHandler.latestVersion(); + String latestEdgeVersion = NetworkHandler.latestEdgeVersion(); if (latestVersion != null) { versionCheck = " (" + Phrase.build(Phrase.LATEST_VERSION, "v" + latestVersion) + ")"; } + else if (latestEdgeVersion != null && !VersionUtils.isCommunityEdition()) { + versionCheck = " (" + Phrase.build(Phrase.LATEST_VERSION, "v" + latestEdgeVersion) + ")"; + } } Chat.sendMessage(player, Color.WHITE + "----- " + Color.DARK_AQUA + "CoreProtect" + (VersionUtils.isCommunityEdition() ? " " + ConfigHandler.COMMUNITY_EDITION : "") + Color.WHITE + " -----"); diff --git a/src/main/java/net/coreprotect/config/ConfigHandler.java b/src/main/java/net/coreprotect/config/ConfigHandler.java index 6effac7..bf59112 100644 --- a/src/main/java/net/coreprotect/config/ConfigHandler.java +++ b/src/main/java/net/coreprotect/config/ConfigHandler.java @@ -40,6 +40,11 @@ import net.coreprotect.utility.VersionUtils; import oshi.hardware.CentralProcessor; public class ConfigHandler extends Queue { + + public enum CacheType { + MATERIALS, BLOCKDATA, ART, ENTITIES, WORLDS + } + public static int SERVER_VERSION = 0; public static final int EDITION_VERSION = 2; public static final String EDITION_BRANCH = VersionUtils.getBranch(); @@ -47,7 +52,8 @@ public class ConfigHandler extends Queue { public static final String COMMUNITY_EDITION = "Community Edition"; public static final String JAVA_VERSION = "11.0"; public static final String MINECRAFT_VERSION = "1.16"; - public static final String LATEST_VERSION = "1.21.8"; + public static final String PATCH_VERSION = "23.0"; + public static final String LATEST_VERSION = "1.21.10"; public static String path = "plugins/CoreProtect/"; public static String sqlite = "database.db"; public static String host = "127.0.0.1"; @@ -268,7 +274,7 @@ public class ConfigHandler extends Queue { Database.createDatabaseTables(ConfigHandler.prefix, false, null, Config.getGlobal().MYSQL, false); } - public static void loadTypes(Statement statement) { + public static void loadMaterials(Statement statement) { try { String query = "SELECT id,material FROM " + ConfigHandler.prefix + "material_map"; ResultSet rs = statement.executeQuery(query); @@ -286,9 +292,16 @@ public class ConfigHandler extends Queue { } } rs.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } - query = "SELECT id,data FROM " + ConfigHandler.prefix + "blockdata_map"; - rs = statement.executeQuery(query); + public static void loadBlockdata(Statement statement) { + try { + String query = "SELECT id,data FROM " + ConfigHandler.prefix + "blockdata_map"; + ResultSet rs = statement.executeQuery(query); ConfigHandler.blockdata.clear(); ConfigHandler.blockdataReversed.clear(); blockdataId = 0; @@ -303,9 +316,16 @@ public class ConfigHandler extends Queue { } } rs.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } - query = "SELECT id,art FROM " + ConfigHandler.prefix + "art_map"; - rs = statement.executeQuery(query); + public static void loadArt(Statement statement) { + try { + String query = "SELECT id,art FROM " + ConfigHandler.prefix + "art_map"; + ResultSet rs = statement.executeQuery(query); ConfigHandler.art.clear(); ConfigHandler.artReversed.clear(); artId = 0; @@ -320,9 +340,16 @@ public class ConfigHandler extends Queue { } } rs.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + } - query = "SELECT id,entity FROM " + ConfigHandler.prefix + "entity_map"; - rs = statement.executeQuery(query); + public static void loadEntities(Statement statement) { + try { + String query = "SELECT id,entity FROM " + ConfigHandler.prefix + "entity_map"; + ResultSet rs = statement.executeQuery(query); ConfigHandler.entities.clear(); ConfigHandler.entitiesReversed.clear(); entityId = 0; @@ -343,6 +370,67 @@ public class ConfigHandler extends Queue { } } + public static void loadTypes(Statement statement) { + loadMaterials(statement); + loadBlockdata(statement); + loadArt(statement); + loadEntities(statement); + } + + /** + * Unified method to reload cache from database when DATABASE_LOCK is false (multi-server setup) + * + * @param type + * The type of cache to reload + * @param name + * The name to look up after reload + * @return The ID if found after reload, or -1 if not found + */ + public static int reloadAndGetId(CacheType type, String name) { + // Only reload if DATABASE_LOCK is false (multi-server setup) + if (Config.getGlobal().DATABASE_LOCK) { + return -1; + } + + try (Connection connection = Database.getConnection(true)) { + if (connection != null) { + Statement statement = connection.createStatement(); + + // Reload appropriate cache based on type + switch (type) { + case MATERIALS: + loadMaterials(statement); + statement.close(); + return materials.getOrDefault(name, -1); + case BLOCKDATA: + loadBlockdata(statement); + statement.close(); + return blockdata.getOrDefault(name, -1); + case ART: + loadArt(statement); + statement.close(); + return art.getOrDefault(name, -1); + case ENTITIES: + loadEntities(statement); + statement.close(); + return entities.getOrDefault(name, -1); + case WORLDS: + loadWorlds(statement); + statement.close(); + return worlds.getOrDefault(name, -1); + default: + statement.close(); + return -1; + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + + return -1; + } + public static void loadWorlds(Statement statement) { try { String query = "SELECT id,world FROM " + ConfigHandler.prefix + "world"; diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index d566fa4..21788c8 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -209,7 +209,7 @@ public class RollbackProcessor { BlockData checkData = rowType == Material.AIR ? blockData : rawBlockData; if (checkData != null) { if (checkData.getAsString().equals(pendingChangeData.getAsString()) || checkData instanceof org.bukkit.block.data.MultipleFacing || checkData instanceof org.bukkit.block.data.type.Stairs || checkData instanceof org.bukkit.block.data.type.RedstoneWire) { - if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST) { // always update double chests + if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST && !BukkitAdapter.ADAPTER.isCopperChest(rowType)) { // always update double chests changeBlock = false; } } diff --git a/src/main/java/net/coreprotect/listener/player/InventoryChangeListener.java b/src/main/java/net/coreprotect/listener/player/InventoryChangeListener.java index cbb372c..5db038e 100644 --- a/src/main/java/net/coreprotect/listener/player/InventoryChangeListener.java +++ b/src/main/java/net/coreprotect/listener/player/InventoryChangeListener.java @@ -10,12 +10,14 @@ import java.util.concurrent.atomic.AtomicLong; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.DoubleChest; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryDragEvent; @@ -27,6 +29,7 @@ import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import net.coreprotect.CoreProtect; +import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.config.Config; import net.coreprotect.config.ConfigHandler; import net.coreprotect.consumer.Queue; @@ -342,9 +345,58 @@ public final class InventoryChangeListener extends Queue implements Listener { return true; } + private boolean checkCrafterSlotChange(InventoryClickEvent event) { + // Check if the clicked inventory is a crafter + if (!BukkitAdapter.ADAPTER.isCrafter(event.getInventory().getType())) { + return false; + } + + // Check that the Action is NOTHING + if (event.getAction() != InventoryAction.NOTHING) { + return false; + } + + // Check if the clicked slot is one of the crafter slots + if (event.getRawSlot() < 0 || event.getRawSlot() > 8) { + return false; + } + + // Check that the click type is not a middle click + if (!(event.getClick() == ClickType.LEFT || event.getClick() == ClickType.RIGHT)) { + return false; + } + + // Gather other necessary information + Player player = (Player) event.getWhoClicked(); + Inventory inventory = event.getInventory(); + + Location location = null; + try { + location = inventory.getLocation(); + } + catch (Exception e) { + return false; + } + + if (location == null) { + return false; + } + + Block block = location.getBlock(); + BlockState blockState = block.getState(); + + Queue.queueBlockPlace(player.getName(), blockState, block.getType(), blockState, block.getType(), -1, 0, blockState.getBlockData().getAsString()); + return true; + } + @EventHandler(priority = EventPriority.LOWEST) protected void onInventoryClick(InventoryClickEvent event) { InventoryAction inventoryAction = event.getAction(); + + if (checkCrafterSlotChange(event)) { + return; + } + if (inventoryAction == InventoryAction.NOTHING) { return; } diff --git a/src/main/java/net/coreprotect/listener/player/PlayerInteractListener.java b/src/main/java/net/coreprotect/listener/player/PlayerInteractListener.java index 35c997f..680aa61 100755 --- a/src/main/java/net/coreprotect/listener/player/PlayerInteractListener.java +++ b/src/main/java/net/coreprotect/listener/player/PlayerInteractListener.java @@ -145,7 +145,7 @@ public final class PlayerInteractListener extends Queue implements Listener { } else if (isContainerBlock && Config.getConfig(world).ITEM_TRANSACTIONS) { Location location = null; - if (type.equals(Material.CHEST) || type.equals(Material.TRAPPED_CHEST)) { + if (type.equals(Material.CHEST) || type.equals(Material.TRAPPED_CHEST) || BukkitAdapter.ADAPTER.isCopperChest(type)) { Chest chest = (Chest) clickedBlock.getState(); InventoryHolder inventoryHolder = chest.getInventory().getHolder(); diff --git a/src/main/java/net/coreprotect/services/VersionCheckService.java b/src/main/java/net/coreprotect/services/VersionCheckService.java index dba40c8..68aeb64 100644 --- a/src/main/java/net/coreprotect/services/VersionCheckService.java +++ b/src/main/java/net/coreprotect/services/VersionCheckService.java @@ -44,6 +44,13 @@ public class VersionCheckService { return false; } + // Patch version validation + if (VersionUtils.newVersion(ConfigHandler.PATCH_VERSION, VersionUtils.getPluginVersion()) && !VersionUtils.isBranch("dev")) { + Chat.console(Phrase.build(Phrase.VERSION_INCOMPATIBLE, "CoreProtect", "v" + VersionUtils.getPluginVersion())); + Chat.sendConsoleMessage(Color.GREY + "[CoreProtect] " + Phrase.build(Phrase.INVALID_BRANCH_2)); + return false; + } + // Branch validation if (ConfigHandler.EDITION_BRANCH.length() == 0) { Chat.sendConsoleMessage(Color.RED + "[CoreProtect] " + Phrase.build(Phrase.INVALID_BRANCH_1)); diff --git a/src/main/java/net/coreprotect/utility/BlockUtils.java b/src/main/java/net/coreprotect/utility/BlockUtils.java index b8eb786..b0bd680 100644 --- a/src/main/java/net/coreprotect/utility/BlockUtils.java +++ b/src/main/java/net/coreprotect/utility/BlockUtils.java @@ -207,7 +207,8 @@ public class BlockUtils { try { if (blockState instanceof BlockInventoryHolder) { if (singleBlock) { - List chests = java.util.Arrays.asList(Material.CHEST, Material.TRAPPED_CHEST); + List chests = new java.util.ArrayList<>(java.util.Arrays.asList(Material.CHEST, Material.TRAPPED_CHEST)); + chests.addAll(BukkitAdapter.ADAPTER.copperChestMaterials()); Material type = blockState.getType(); if (chests.contains(type)) { inventory = ((org.bukkit.block.Chest) blockState).getBlockInventory(); diff --git a/src/main/java/net/coreprotect/utility/EntityUtils.java b/src/main/java/net/coreprotect/utility/EntityUtils.java index 342e75f..7e1ab37 100644 --- a/src/main/java/net/coreprotect/utility/EntityUtils.java +++ b/src/main/java/net/coreprotect/utility/EntityUtils.java @@ -33,6 +33,12 @@ public class EntityUtils extends Queue { id = ConfigHandler.entities.get(name); } else if (internal) { + // Check if another server has already added this entity (multi-server setup) + id = ConfigHandler.reloadAndGetId(ConfigHandler.CacheType.ENTITIES, name); + if (id != -1) { + return id; + } + int entityID = ConfigHandler.entityId + 1; ConfigHandler.entities.put(name, entityID); ConfigHandler.entitiesReversed.put(entityID, name); diff --git a/src/main/java/net/coreprotect/utility/MaterialUtils.java b/src/main/java/net/coreprotect/utility/MaterialUtils.java index f0f568e..25129cd 100644 --- a/src/main/java/net/coreprotect/utility/MaterialUtils.java +++ b/src/main/java/net/coreprotect/utility/MaterialUtils.java @@ -35,6 +35,12 @@ public class MaterialUtils extends Queue { id = ConfigHandler.materials.get(name); } else if (internal) { + // Check if another server has already added this material (multi-server setup) + id = ConfigHandler.reloadAndGetId(ConfigHandler.CacheType.MATERIALS, name); + if (id != -1) { + return id; + } + int mid = ConfigHandler.materialId + 1; ConfigHandler.materials.put(name, mid); ConfigHandler.materialsReversed.put(mid, name); @@ -54,6 +60,12 @@ public class MaterialUtils extends Queue { id = ConfigHandler.blockdata.get(data); } else if (internal) { + // Check if another server has already added this blockdata (multi-server setup) + id = ConfigHandler.reloadAndGetId(ConfigHandler.CacheType.BLOCKDATA, data); + if (id != -1) { + return id; + } + int bid = ConfigHandler.blockdataId + 1; ConfigHandler.blockdata.put(data, bid); ConfigHandler.blockdataReversed.put(bid, data); @@ -135,6 +147,12 @@ public class MaterialUtils extends Queue { id = ConfigHandler.art.get(name); } else if (internal) { + // Check if another server has already added this art (multi-server setup) + id = ConfigHandler.reloadAndGetId(ConfigHandler.CacheType.ART, name); + if (id != -1) { + return id; + } + int artID = ConfigHandler.artId + 1; ConfigHandler.art.put(name, artID); ConfigHandler.artReversed.put(artID, name); diff --git a/src/main/java/net/coreprotect/utility/WorldUtils.java b/src/main/java/net/coreprotect/utility/WorldUtils.java index 4af911e..a63b5ab 100644 --- a/src/main/java/net/coreprotect/utility/WorldUtils.java +++ b/src/main/java/net/coreprotect/utility/WorldUtils.java @@ -16,6 +16,12 @@ public class WorldUtils extends Queue { int id = -1; try { if (ConfigHandler.worlds.get(name) == null) { + // Check if another server has already added this world (multi-server setup) + id = ConfigHandler.reloadAndGetId(ConfigHandler.CacheType.WORLDS, name); + if (id != -1) { + return id; + } + int wid = ConfigHandler.worldId + 1; ConfigHandler.worlds.put(name, wid); ConfigHandler.worldsReversed.put(wid, name); @@ -84,7 +90,7 @@ public class WorldUtils extends Queue { return id; } - + public static String getWidIndex(String queryTable) { String index = ""; boolean isMySQL = net.coreprotect.config.Config.getGlobal().MYSQL; @@ -121,4 +127,4 @@ public class WorldUtils extends Queue { return index; } -} \ No newline at end of file +}