Compare commits

...

10 commits

Author SHA1 Message Date
Intelli
49c0f16934 Whoops 2025-10-10 11:26:59 -06:00
Intelli
322c630af4 Bump supported MC version to 1.21.10 2025-10-10 11:26:15 -06:00
Intelli
251b4c56cd Fixed NoSuchFieldError in MC 1.20.5 and earlier 2025-10-02 11:22:59 -06:00
Intelli
9de1d16836 Bump supported MC version to 1.21.9 2025-10-02 10:38:36 -06:00
Intelli
f5109122ac Added edge version handling in status command 2025-10-01 20:07:18 -06:00
Intelli
4a8dd716f4 Fixed UnsupportedOperationException 2025-10-01 20:07:03 -06:00
Intelli
6572946b3d Added support for copper chests 2025-10-01 19:06:00 -06:00
Intelli
51d4bf27cd Added MultiPaper support 2025-09-26 14:12:00 -06:00
Artem Dmitriev
d30f6cf8d5
Update ru.yml (#798)
"Выберете" instead of "Выберите"
2025-09-12 18:04:05 -06:00
Stuffy
f96556a61f
Crafter Slot Enable/Disable Support (fixes #766) (#767)
* Crafter Slot Enable/Disable Support (fixes #766)

* Exclude negative slot values
2025-09-09 10:34:43 -06:00
16 changed files with 295 additions and 15 deletions

View file

@ -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: "Предварительный просмотр откатов доступен только в игре."

25
pom.xml
View file

@ -113,6 +113,31 @@
<skipTests>${skipTests}</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireNoRepositories>
<allowedRepositories>
<allowedRepository>central</allowedRepository>
<allowedRepository>enginehub-repo</allowedRepository>
<allowedRepository>spigot-repo</allowedRepository>
<allowedRepository>paper-repo</allowedRepository>
<allowedRepository>codemc-repo</allowedRepository>
<allowedRepository>jitpack.io</allowedRepository>
</allowedRepositories>
</requireNoRepositories>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>

View file

@ -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<Material> 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<Material> copperChestMaterials() {
return EMPTY_SET;
}
}

View file

@ -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<Material> copperChestMaterials();
}

View file

@ -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<Material> 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<Material> 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;
}
}

View file

@ -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 + " -----");

View file

@ -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";

View file

@ -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;
}
}

View file

@ -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;
}

View file

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

View file

@ -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));

View file

@ -207,7 +207,8 @@ public class BlockUtils {
try {
if (blockState instanceof BlockInventoryHolder) {
if (singleBlock) {
List<Material> chests = java.util.Arrays.asList(Material.CHEST, Material.TRAPPED_CHEST);
List<Material> 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();

View file

@ -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);

View file

@ -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);

View file

@ -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);