diff --git a/src/main/java/net/coreprotect/database/rollback/Rollback.java b/src/main/java/net/coreprotect/database/rollback/Rollback.java index b20c71c..499ae12 100644 --- a/src/main/java/net/coreprotect/database/rollback/Rollback.java +++ b/src/main/java/net/coreprotect/database/rollback/Rollback.java @@ -1,62 +1,22 @@ package net.coreprotect.database.rollback; -import java.io.ByteArrayInputStream; import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; -import java.util.UUID; import org.bukkit.Bukkit; -import org.bukkit.DyeColor; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.block.Banner; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; -import org.bukkit.block.CommandBlock; -import org.bukkit.block.CreatureSpawner; -import org.bukkit.block.Jukebox; -import org.bukkit.block.banner.Pattern; -import org.bukkit.block.data.Bisected; -import org.bukkit.block.data.Bisected.Half; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.MultipleFacing; -import org.bukkit.block.data.Waterlogged; -import org.bukkit.block.data.type.Bed; -import org.bukkit.block.data.type.Bed.Part; -import org.bukkit.block.data.type.Chest; -import org.bukkit.block.data.type.Door; -import org.bukkit.block.data.type.Door.Hinge; -import org.bukkit.block.data.type.Piston; -import org.bukkit.block.data.type.PistonHead; -import org.bukkit.block.data.type.RedstoneWire; -import org.bukkit.block.data.type.Snow; -import org.bukkit.block.data.type.Stairs; -import org.bukkit.block.data.type.TechnicalPiston; -import org.bukkit.block.data.type.TrapDoor; import org.bukkit.command.CommandSender; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.EnderCrystal; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.ItemFrame; -import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; -import org.bukkit.inventory.EntityEquipment; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.io.BukkitObjectInputStream; import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; @@ -65,26 +25,15 @@ import net.coreprotect.config.ConfigHandler; import net.coreprotect.consumer.Queue; import net.coreprotect.consumer.process.Process; import net.coreprotect.database.Lookup; -import net.coreprotect.database.logger.ItemLogger; import net.coreprotect.database.statement.UserStatement; import net.coreprotect.language.Phrase; import net.coreprotect.language.Selector; import net.coreprotect.model.BlockGroup; -import net.coreprotect.paper.PaperAdapter; -import net.coreprotect.thread.CacheHandler; import net.coreprotect.thread.Scheduler; -import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.Chat; -import net.coreprotect.utility.ChestTool; import net.coreprotect.utility.Color; import net.coreprotect.utility.DatabaseUtils; -import net.coreprotect.utility.EntityUtils; -import net.coreprotect.utility.ItemUtils; -import net.coreprotect.utility.MaterialUtils; -import net.coreprotect.utility.Teleport; -import net.coreprotect.utility.Util; import net.coreprotect.utility.WorldUtils; -import net.coreprotect.utility.entity.HangingUtil; public class Rollback extends RollbackUtil { @@ -140,19 +89,6 @@ public class Rollback extends RollbackUtil { HashMap>> itemDataList = new HashMap<>(); boolean inventoryRollback = actionList.contains(11); - /* - int worldMin = BukkitAdapter.ADAPTER.getMinHeight(world); - int worldHeight = world.getMaxHeight() - worldMin; - int negativeOffset = (-(worldMin) >> 4); - Integer[] chunkSections = new Integer[worldHeight >> 4]; - - int y = -2044; - if (y < worldMin) { - return; - } - int chunkSection = ((y >> 4) + negativeOffset); - */ - int worldId = -1; int worldMin = 0; int worldMax = 2032; @@ -174,9 +110,6 @@ public class Rollback extends RollbackUtil { int chunkX = rowX >> 4; int chunkZ = rowZ >> 4; long chunkKey = inventoryRollback ? 0 : (chunkX & 0xffffffffL | (chunkZ & 0xffffffffL) << 32); - // int rowAction = result[8]; - // if (rowAction==10) result[8] = 0; - // if (rowAction==11) result[8] = 1; if (rowWorldId != worldId) { String world = WorldUtils.getWorldName(rowWorldId); @@ -187,12 +120,6 @@ public class Rollback extends RollbackUtil { } } - /* - if (rowY < worldMin) { - continue; - } - */ - if (chunkList.get(chunkKey) == null) { int distance = 0; if (location != null) { @@ -218,8 +145,6 @@ public class Rollback extends RollbackUtil { } if (modifyList.get(rowWorldId).get(chunkKey) == null) { - // Integer[][] chunkSections = new Integer[((worldMax - worldMin) >> 4)][]; - // adjacentDataList.put(chunkKey, chunkSections); dataList.get(rowWorldId).put(chunkKey, new ArrayList<>()); itemDataList.get(rowWorldId).put(chunkKey, new ArrayList<>()); } @@ -329,941 +254,10 @@ public class Rollback extends RollbackUtil { final HashMap> finalItemList = itemDataList.get(rollbackWorldId); Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { - try { - boolean clearInventories = false; - if (Config.getGlobal().ROLLBACK_ITEMS) { - clearInventories = true; - } - - ArrayList data = finalBlockList.getOrDefault(chunkKey, new ArrayList<>()); - ArrayList itemData = finalItemList.getOrDefault(chunkKey, new ArrayList<>()); - Map chunkChanges = new LinkedHashMap<>(); - - for (Object[] row : data) { - int unixtimestamp = (int) (System.currentTimeMillis() / 1000L); - int[] rollbackHashData1 = ConfigHandler.rollbackHash.get(finalUserString); - int itemCount1 = rollbackHashData1[0]; - int blockCount1 = rollbackHashData1[1]; - int entityCount1 = rollbackHashData1[2]; - int scannedWorlds = rollbackHashData1[4]; - // int rowId = row[0]; - int rowTime = (Integer) row[1]; - int rowUserId = (Integer) row[2]; - int rowX = (Integer) row[3]; - int rowY = (Integer) row[4]; - int rowZ = (Integer) row[5]; - int rowTypeRaw = (Integer) row[6]; - int rowData = (Integer) row[7]; - int rowAction = (Integer) row[8]; - int rowRolledBack = MaterialUtils.rolledBack((Integer) row[9], false); - int rowWorldId = (Integer) row[10]; - byte[] rowMeta = (byte[]) row[12]; - byte[] rowBlockData = (byte[]) row[13]; - String blockDataString = BlockUtils.byteDataToString(rowBlockData, rowTypeRaw); - Material rowType = MaterialUtils.getType(rowTypeRaw); - - List meta = null; - if (rowMeta != null) { - ByteArrayInputStream metaByteStream = new ByteArrayInputStream(rowMeta); - BukkitObjectInputStream metaObjectStream = new BukkitObjectInputStream(metaByteStream); - @SuppressWarnings("unchecked") - List metaList = (List) metaObjectStream.readObject(); - metaObjectStream.close(); - metaByteStream.close(); - meta = metaList; - } - - BlockData blockData = null; - if (blockDataString != null && blockDataString.contains(":")) { - try { - blockData = Bukkit.getServer().createBlockData(blockDataString); - } - catch (Exception e) { - // corrupt BlockData, let the server automatically set the BlockData instead - } - } - - BlockData rawBlockData = null; - if (blockData != null) { - rawBlockData = blockData.clone(); - } - if (rawBlockData == null && rowType != null && rowType.isBlock()) { - rawBlockData = BlockUtils.createBlockData(rowType); - } - - String rowUser = ConfigHandler.playerIdCacheReversed.get(rowUserId); - int oldTypeRaw = rowTypeRaw; - Material oldTypeMaterial = MaterialUtils.getType(oldTypeRaw); - - if (rowAction == 1 && rollbackType == 0) { // block placement - rowType = Material.AIR; - blockData = null; - rowTypeRaw = 0; - } - else if (rowAction == 0 && rollbackType == 1) { // block removal - rowType = Material.AIR; - blockData = null; - rowTypeRaw = 0; - } - else if (rowAction == 4 && rollbackType == 0) { // entity placement - rowType = null; - rowTypeRaw = 0; - } - else if (rowAction == 3 && rollbackType == 1) { // entity removal - rowType = null; - rowTypeRaw = 0; - } - - if (preview > 0) { - if (rowAction != 3) { // entity kill - String world = WorldUtils.getWorldName(rowWorldId); - if (world.length() == 0) { - continue; - } - - World bukkitWorld = Bukkit.getServer().getWorld(world); - if (bukkitWorld == null) { - continue; - } - - Block block = new Location(bukkitWorld, rowX, rowY, rowZ).getBlock(); - if (preview == 2) { - Material blockType = block.getType(); - if (!BukkitAdapter.ADAPTER.isItemFrame(blockType) && !blockType.equals(Material.PAINTING) && !blockType.equals(Material.ARMOR_STAND) && !blockType.equals(Material.END_CRYSTAL)) { - BlockUtils.prepareTypeAndData(chunkChanges, block, blockType, block.getBlockData(), true); - blockCount1++; - } - } - else { - if ((!BukkitAdapter.ADAPTER.isItemFrame(rowType)) && (rowType != Material.PAINTING) && (rowType != Material.ARMOR_STAND) && (rowType != Material.END_CRYSTAL)) { - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); - blockCount1++; - } - } - } - else { - entityCount1++; - } - } - else if (rowAction == 3) { // entity kill - String world = WorldUtils.getWorldName(rowWorldId); - if (world.length() == 0) { - continue; - } - - World bukkitWorld = Bukkit.getServer().getWorld(world); - if (bukkitWorld == null) { - continue; - } - Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (!bukkitWorld.isChunkLoaded(block.getChunk())) { - bukkitWorld.getChunkAt(block.getLocation()); - } - - if (rowTypeRaw > 0) { - // spawn in entity - if (rowRolledBack == 0) { - EntityType entity_type = EntityUtils.getEntityType(rowTypeRaw); - Queue.queueEntitySpawn(rowUser, block.getState(), entity_type, rowData); - entityCount1++; - } - } - else if (oldTypeRaw > 0) { - // attempt to remove entity - if (rowRolledBack == 1) { - boolean removed = false; - int entityId = -1; - String entityName = EntityUtils.getEntityType(oldTypeRaw).name(); - String token = "" + rowX + "." + rowY + "." + rowZ + "." + rowWorldId + "." + entityName + ""; - Object[] cachedEntity = CacheHandler.entityCache.get(token); - - if (cachedEntity != null) { - entityId = (Integer) cachedEntity[1]; - } - - int xmin = rowX - 5; - int xmax = rowX + 5; - int ymin = rowY - 1; - int ymax = rowY + 1; - int zmin = rowZ - 5; - int zmax = rowZ + 5; - - for (Entity entity : block.getChunk().getEntities()) { - if (entityId > -1) { - int id = entity.getEntityId(); - if (id == entityId) { - entityCount1++; - removed = true; - entity.remove(); - break; - } - } - else { - if (entity.getType().equals(EntityUtils.getEntityType(oldTypeRaw))) { - Location entityLocation = entity.getLocation(); - int entityx = entityLocation.getBlockX(); - int entityY = entityLocation.getBlockY(); - int entityZ = entityLocation.getBlockZ(); - - if (entityx >= xmin && entityx <= xmax && entityY >= ymin && entityY <= ymax && entityZ >= zmin && entityZ <= zmax) { - entityCount1++; - removed = true; - entity.remove(); - break; - } - } - } - } - - if (!removed && entityId > -1) { - for (Entity entity : block.getWorld().getLivingEntities()) { - int id = entity.getEntityId(); - if (id == entityId) { - entityCount1++; - removed = true; - entity.remove(); - break; - } - } - } - } - } - } - else { - if (rowType == null) { - continue; - } - - String world = WorldUtils.getWorldName(rowWorldId); - if (world.length() == 0) { - continue; - } - - World bukkitWorld = Bukkit.getServer().getWorld(world); - if (bukkitWorld == null) { - continue; - } - Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (!bukkitWorld.isChunkLoaded(block.getChunk())) { - bukkitWorld.getChunkAt(block.getLocation()); - } - - boolean changeBlock = true; - boolean countBlock = true; - Material changeType = block.getType(); - BlockData changeBlockData = block.getBlockData(); - BlockData pendingChangeData = chunkChanges.get(block); - Material pendingChangeType = changeType; - if (pendingChangeData != null) { - pendingChangeType = pendingChangeData.getMaterial(); - } - else { - pendingChangeData = changeBlockData; - } - - if (rowRolledBack == 1 && rollbackType == 0) { // rollback - countBlock = false; - } - - if ((rowType == pendingChangeType) && ((!BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { - // block is already changed! - BlockData checkData = rowType == Material.AIR ? blockData : rawBlockData; - if (checkData != null) { - if (checkData.getAsString().equals(pendingChangeData.getAsString()) || checkData instanceof MultipleFacing || checkData instanceof Stairs || checkData instanceof RedstoneWire) { - if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST) { // always update double chests - changeBlock = false; - } - } - } - else if (rowType == Material.AIR) { - changeBlock = false; - } - - countBlock = false; - } - else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.CAVE_AIR)) { - countBlock = true; - } - - if ((pendingChangeType == Material.WATER) && (rowType != Material.AIR) && (rowType != Material.CAVE_AIR) && blockData != null) { - if (blockData instanceof Waterlogged) { - if (Material.WATER.createBlockData().equals(block.getBlockData())) { - Waterlogged waterlogged = (Waterlogged) blockData; - waterlogged.setWaterlogged(true); - } - } - } - - try { - if (changeBlock) { - /* If modifying the head of a piston, update the base piston block to prevent it from being destroyed */ - if (changeBlockData instanceof PistonHead) { - PistonHead pistonHead = (PistonHead) changeBlockData; - Block pistonBlock = block.getRelative(pistonHead.getFacing().getOppositeFace()); - BlockData pistonData = pistonBlock.getBlockData(); - if (pistonData instanceof Piston) { - Piston piston = (Piston) pistonData; - piston.setExtended(false); - pistonBlock.setBlockData(piston, false); - } - } - else if (rowType == Material.MOVING_PISTON && blockData instanceof TechnicalPiston && !(blockData instanceof PistonHead)) { - TechnicalPiston technicalPiston = (TechnicalPiston) blockData; - rowType = (technicalPiston.getType() == org.bukkit.block.data.type.TechnicalPiston.Type.STICKY ? Material.STICKY_PISTON : Material.PISTON); - blockData = rowType.createBlockData(); - ((Piston) blockData).setFacing(technicalPiston.getFacing()); - } - - if ((rowType == Material.AIR) && ((BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) || (oldTypeMaterial == Material.PAINTING))) { - HangingUtil.removeHanging(block.getState(), blockDataString); - } - else if ((BukkitAdapter.ADAPTER.isItemFrame(rowType)) || (rowType == Material.PAINTING)) { - HangingUtil.spawnHanging(block.getState(), rowType, blockDataString, rowData); - } - else if ((rowType == Material.ARMOR_STAND)) { - Location location1 = block.getLocation(); - location1.setX(location1.getX() + 0.50); - location1.setZ(location1.getZ() + 0.50); - location1.setYaw(rowData); - boolean exists = false; - - for (Entity entity : block.getChunk().getEntities()) { - if (entity instanceof ArmorStand) { - if (entity.getLocation().getBlockX() == location1.getBlockX() && entity.getLocation().getBlockY() == location1.getBlockY() && entity.getLocation().getBlockZ() == location1.getBlockZ()) { - exists = true; - } - } - } - - if (!exists) { - Entity entity = block.getLocation().getWorld().spawnEntity(location1, EntityType.ARMOR_STAND); - PaperAdapter.ADAPTER.teleportAsync(entity, location1); - } - } - else if ((rowType == Material.END_CRYSTAL)) { - Location location1 = block.getLocation(); - location1.setX(location1.getX() + 0.50); - location1.setZ(location1.getZ() + 0.50); - boolean exists = false; - - for (Entity entity : block.getChunk().getEntities()) { - if (entity instanceof EnderCrystal) { - if (entity.getLocation().getBlockX() == location1.getBlockX() && entity.getLocation().getBlockY() == location1.getBlockY() && entity.getLocation().getBlockZ() == location1.getBlockZ()) { - exists = true; - } - } - } - - if (!exists) { - Entity entity = block.getLocation().getWorld().spawnEntity(location1, BukkitAdapter.ADAPTER.getEntityType(Material.END_CRYSTAL)); - EnderCrystal enderCrystal = (EnderCrystal) entity; - enderCrystal.setShowingBottom((rowData != 0)); - PaperAdapter.ADAPTER.teleportAsync(entity, location1); - } - } - else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.WATER))) { - if (pendingChangeData instanceof Waterlogged) { - Waterlogged waterlogged = (Waterlogged) pendingChangeData; - waterlogged.setWaterlogged(false); - BlockUtils.prepareTypeAndData(chunkChanges, block, null, waterlogged, false); - } - else { - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); - } - - if (countBlock) { - blockCount1++; - } - } - else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.SNOW))) { - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); - if (countBlock) { - blockCount1++; - } - } - else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.END_CRYSTAL))) { - for (Entity entity : block.getChunk().getEntities()) { - if (entity instanceof EnderCrystal) { - if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) { - entity.remove(); - } - } - } - } - else if (rollbackType == 0 && rowAction == 0 && (rowType == Material.AIR)) { - // broke block ID #0 - } - else if ((rowType == Material.AIR) || (rowType == Material.TNT)) { - if (clearInventories) { - if (BlockGroup.CONTAINERS.contains(changeType)) { - Inventory inventory = BlockUtils.getContainerInventory(block.getState(), false); - if (inventory != null) { - inventory.clear(); - } - } - else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND)) { - if ((oldTypeMaterial == Material.ARMOR_STAND)) { - for (Entity entity : block.getChunk().getEntities()) { - if (entity instanceof ArmorStand) { - Location entityLocation = entity.getLocation(); - entityLocation.setY(entityLocation.getY() + 0.99); - - if (entityLocation.getBlockX() == rowX && entityLocation.getBlockY() == rowY && entityLocation.getBlockZ() == rowZ) { - EntityEquipment equipment = ItemUtils.getEntityEquipment((LivingEntity) entity); - if (equipment != null) { - equipment.clear(); - } - - entityLocation.setY(entityLocation.getY() - 1.99); - PaperAdapter.ADAPTER.teleportAsync(entity, entityLocation); - entity.remove(); - } - } - } - } - } - } - - boolean remove = true; - if ((rowType == Material.AIR)) { - if (pendingChangeData instanceof Waterlogged) { - Waterlogged waterlogged = (Waterlogged) pendingChangeData; - if (waterlogged.isWaterlogged()) { - BlockUtils.prepareTypeAndData(chunkChanges, block, Material.WATER, Material.WATER.createBlockData(), true); - remove = false; - } - } - else if ((pendingChangeType == Material.WATER)) { - if (rawBlockData instanceof Waterlogged) { - Waterlogged waterlogged = (Waterlogged) rawBlockData; - if (waterlogged.isWaterlogged()) { - remove = false; - } - } - } - } - - if (remove) { - boolean physics = true; - if ((changeType == Material.NETHER_PORTAL) || changeBlockData instanceof MultipleFacing || changeBlockData instanceof Snow || changeBlockData instanceof Stairs || changeBlockData instanceof RedstoneWire || changeBlockData instanceof Chest) { - physics = true; - } - else if (changeBlockData instanceof Bisected && !(changeBlockData instanceof TrapDoor)) { - Bisected bisected = (Bisected) changeBlockData; - Location bisectLocation = block.getLocation().clone(); - if (bisected.getHalf() == Half.TOP) { - bisectLocation.setY(bisectLocation.getY() - 1); - } - else { - bisectLocation.setY(bisectLocation.getY() + 1); - } - - int worldMaxHeight = bukkitWorld.getMaxHeight(); - int worldMinHeight = BukkitAdapter.ADAPTER.getMinHeight(bukkitWorld); - if (bisectLocation.getBlockY() >= worldMinHeight && bisectLocation.getBlockY() < worldMaxHeight) { - Block bisectBlock = block.getWorld().getBlockAt(bisectLocation); - BlockUtils.prepareTypeAndData(chunkChanges, bisectBlock, rowType, null, false); - - if (countBlock) { - blockCount1++; - } - } - } - else if (changeBlockData instanceof Bed) { - Bed bed = (Bed) changeBlockData; - if (bed.getPart() == Part.FOOT) { - Block adjacentBlock = block.getRelative(bed.getFacing()); - BlockUtils.prepareTypeAndData(chunkChanges, adjacentBlock, rowType, null, false); - } - } - - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, null, physics); - } - - if (countBlock) { - blockCount1++; - } - } - else if ((rowType == Material.SPAWNER)) { - try { - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - CreatureSpawner mobSpawner = (CreatureSpawner) block.getState(); - mobSpawner.setSpawnedType(EntityUtils.getSpawnerType(rowData)); - mobSpawner.update(); - - if (countBlock) { - blockCount1++; - } - } - catch (Exception e) { - // e.printStackTrace(); - } - } - else if ((rowType == Material.SKELETON_SKULL) || (rowType == Material.SKELETON_WALL_SKULL) || (rowType == Material.WITHER_SKELETON_SKULL) || (rowType == Material.WITHER_SKELETON_WALL_SKULL) || (rowType == Material.ZOMBIE_HEAD) || (rowType == Material.ZOMBIE_WALL_HEAD) || (rowType == Material.PLAYER_HEAD) || (rowType == Material.PLAYER_WALL_HEAD) || (rowType == Material.CREEPER_HEAD) || (rowType == Material.CREEPER_WALL_HEAD) || (rowType == Material.DRAGON_HEAD) || (rowType == Material.DRAGON_WALL_HEAD)) { // skull - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - if (rowData > 0) { - Queue.queueSkullUpdate(rowUser, block.getState(), rowData); - } - - if (countBlock) { - blockCount1++; - } - } - else if (BukkitAdapter.ADAPTER.isSign(rowType)) {// sign - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - Queue.queueSignUpdate(rowUser, block.getState(), rollbackType, rowTime); - - if (countBlock) { - blockCount1++; - } - } - else if (BlockGroup.SHULKER_BOXES.contains(rowType)) { - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - if (countBlock) { - blockCount1++; - } - if (meta != null) { - Inventory inventory = BlockUtils.getContainerInventory(block.getState(), false); - for (Object value : meta) { - ItemStack item = ItemUtils.unserializeItemStackLegacy(value); - if (item != null) { - modifyContainerItems(rowType, inventory, 0, item, 1); - } - } - } - } - else if (rowType == Material.COMMAND_BLOCK || rowType == Material.REPEATING_COMMAND_BLOCK || rowType == Material.CHAIN_COMMAND_BLOCK) { // command block - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - if (countBlock) { - blockCount1++; - } - - if (meta != null) { - CommandBlock commandBlock = (CommandBlock) block.getState(); - for (Object value : meta) { - if (value instanceof String) { - String string = (String) value; - commandBlock.setCommand(string); - commandBlock.update(); - } - } - } - } - else if ((rowType == Material.WATER)) { - if (pendingChangeData instanceof Waterlogged) { - Waterlogged waterlogged = (Waterlogged) pendingChangeData; - waterlogged.setWaterlogged(true); - BlockUtils.prepareTypeAndData(chunkChanges, block, null, waterlogged, false); - } - else { - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - } - - if (countBlock) { - blockCount1++; - } - } - else if ((rowType == Material.NETHER_PORTAL) && rowAction == 0) { - BlockUtils.prepareTypeAndData(chunkChanges, block, Material.FIRE, null, true); - } - else if (blockData == null && rowData > 0 && (rowType == Material.IRON_DOOR || BlockGroup.DOORS.contains(rowType))) { - if (countBlock) { - blockCount1++; - } - - block.setType(rowType, false); - Door door = (Door) block.getBlockData(); - if (rowData >= 8) { - door.setHalf(Half.TOP); - rowData = rowData - 8; - } - else { - door.setHalf(Half.BOTTOM); - } - if (rowData >= 4) { - door.setHinge(Hinge.RIGHT); - rowData = rowData - 4; - } - else { - door.setHinge(Hinge.LEFT); - } - BlockFace face = BlockFace.NORTH; - - switch (rowData) { - case 0: - face = BlockFace.EAST; - break; - case 1: - face = BlockFace.SOUTH; - break; - case 2: - face = BlockFace.WEST; - break; - } - - door.setFacing(face); - door.setOpen(false); - block.setBlockData(door, false); - } - else if (blockData == null && rowData > 0 && (rowType.name().endsWith("_BED"))) { - if (countBlock) { - blockCount1++; - } - - block.setType(rowType, false); - Bed bed = (Bed) block.getBlockData(); - BlockFace face = BlockFace.NORTH; - - if (rowData > 4) { - bed.setPart(Part.HEAD); - rowData = rowData - 4; - } - - switch (rowData) { - case 2: - face = BlockFace.WEST; - break; - case 3: - face = BlockFace.EAST; - break; - case 4: - face = BlockFace.SOUTH; - break; - } - - bed.setFacing(face); - block.setBlockData(bed, false); - } - else if (rowType.name().endsWith("_BANNER")) { - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - if (countBlock) { - blockCount1++; - } - - if (meta != null) { - Banner banner = (Banner) block.getState(); - - for (Object value : meta) { - if (value instanceof DyeColor) { - banner.setBaseColor((DyeColor) value); - } - else if (value instanceof Map) { - @SuppressWarnings("unchecked") - Pattern pattern = new Pattern((Map) value); - banner.addPattern(pattern); - } - } - - banner.update(); - } - } - else if (rowType != changeType && (BlockGroup.CONTAINERS.contains(rowType) || BlockGroup.CONTAINERS.contains(changeType))) { - block.setType(Material.AIR); // Clear existing container to prevent errors - - boolean isChest = (blockData instanceof Chest); - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, (isChest)); - if (isChest) { - ChestTool.updateDoubleChest(block, blockData, false); - } - - if (countBlock) { - blockCount1++; - } - } - else if (BlockGroup.UPDATE_STATE.contains(rowType) || rowType.name().contains("CANDLE")) { - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); - ChestTool.updateDoubleChest(block, blockData, true); - if (countBlock) { - blockCount1++; - } - } - else if (rowType != Material.AIR && rawBlockData instanceof Bisected && !(rawBlockData instanceof Stairs || rawBlockData instanceof TrapDoor)) { - Bisected bisected = (Bisected) rawBlockData; - Bisected bisectData = (Bisected) rawBlockData.clone(); - Location bisectLocation = block.getLocation().clone(); - if (bisected.getHalf() == Half.TOP) { - bisectData.setHalf(Half.BOTTOM); - bisectLocation.setY(bisectLocation.getY() - 1); - } - else { - bisectData.setHalf(Half.TOP); - bisectLocation.setY(bisectLocation.getY() + 1); - } - - int worldMaxHeight = bukkitWorld.getMaxHeight(); - int worldMinHeight = BukkitAdapter.ADAPTER.getMinHeight(bukkitWorld); - if (bisectLocation.getBlockY() >= worldMinHeight && bisectLocation.getBlockY() < worldMaxHeight) { - Block bisectBlock = block.getWorld().getBlockAt(bisectLocation); - BlockUtils.prepareTypeAndData(chunkChanges, bisectBlock, rowType, bisectData, false); - } - - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - if (countBlock) { - blockCount1++; - blockCount1++; - } - } - else if (rowType != Material.AIR && rawBlockData instanceof Bed) { - Bed bed = (Bed) rawBlockData; - if (bed.getPart() == Part.FOOT) { - Block adjacentBlock = block.getRelative(bed.getFacing()); - Bed bedData = (Bed) rawBlockData.clone(); - bedData.setPart(Part.HEAD); - BlockUtils.prepareTypeAndData(chunkChanges, adjacentBlock, rowType, bedData, false); - if (countBlock) { - blockCount1++; - } - } - - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); - if (countBlock) { - blockCount1++; - } - } - else { - boolean physics = true; - /* - if (blockData instanceof MultipleFacing || BukkitAdapter.ADAPTER.isWall(blockData) || blockData instanceof Snow || blockData instanceof Stairs || blockData instanceof RedstoneWire || blockData instanceof Chest) { - physics = !(blockData instanceof Snow) || block.getY() <= BukkitAdapter.ADAPTER.getMinHeight(block.getWorld()) || (block.getWorld().getBlockAt(block.getX(), block.getY() - 1, block.getZ()).getType().equals(Material.GRASS_BLOCK)); - } - */ - - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, physics); - if (countBlock) { - blockCount1++; - } - } - } - } - catch (Exception e) { - e.printStackTrace(); - } - - if ((rowType != Material.AIR) && changeBlock) { - if (rowUser.length() > 0) { - CacheHandler.lookupCache.put(rowX + "." + rowY + "." + rowZ + "." + rowWorldId, new Object[] { unixtimestamp, rowUser, rowType }); - } - } - } - - // count++; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); - } - data.clear(); - - // Apply cached changes - for (Entry chunkChange : chunkChanges.entrySet()) { - Block changeBlock = chunkChange.getKey(); - BlockData changeBlockData = chunkChange.getValue(); - if (preview > 0) { - Util.sendBlockChange((Player) finalUser, changeBlock.getLocation(), changeBlockData); - } - else { - BlockUtils.setTypeAndData(changeBlock, null, changeBlockData, true); - } - } - chunkChanges.clear(); - - Map> sortPlayers = new HashMap<>(); - Object container = null; - Material containerType = null; - boolean containerInit = false; - int lastX = 0; - int lastY = 0; - int lastZ = 0; - int lastWorldId = 0; - String lastFace = ""; - - for (Object[] row : itemData) { - int[] rollbackHashData1 = ConfigHandler.rollbackHash.get(finalUserString); - int itemCount1 = rollbackHashData1[0]; - int blockCount1 = rollbackHashData1[1]; - int entityCount1 = rollbackHashData1[2]; - int scannedWorlds = rollbackHashData1[4]; - int rowX = (Integer) row[3]; - int rowY = (Integer) row[4]; - int rowZ = (Integer) row[5]; - int rowTypeRaw = (Integer) row[6]; - int rowData = (Integer) row[7]; - int rowAction = (Integer) row[8]; - int rowRolledBack = MaterialUtils.rolledBack((Integer) row[9], false); - int rowWorldId = (Integer) row[10]; - int rowAmount = (Integer) row[11]; - byte[] rowMetadata = (byte[]) row[12]; - Material rowType = MaterialUtils.getType(rowTypeRaw); - - int rolledBackInventory = MaterialUtils.rolledBack((Integer) row[9], true); - if (rowType != null) { - if (inventoryRollback && ((rollbackType == 0 && rolledBackInventory == 0) || (rollbackType == 1 && rolledBackInventory == 1))) { - Material inventoryItem = ItemUtils.itemFilter(rowType, ((Integer) row[14] == 0)); - int rowUserId = (Integer) row[2]; - String rowUser = ConfigHandler.playerIdCacheReversed.get(rowUserId); - if (rowUser == null) { - continue; - } - - String uuid = ConfigHandler.uuidCache.get(rowUser.toLowerCase(Locale.ROOT)); - if (uuid == null) { - continue; - } - - Player player = Bukkit.getServer().getPlayer(UUID.fromString(uuid)); - if (player == null) { - continue; - } - - int inventoryAction = 0; - if (rowAction == ItemLogger.ITEM_DROP || rowAction == ItemLogger.ITEM_PICKUP || rowAction == ItemLogger.ITEM_THROW || rowAction == ItemLogger.ITEM_SHOOT || rowAction == ItemLogger.ITEM_BREAK || rowAction == ItemLogger.ITEM_DESTROY || rowAction == ItemLogger.ITEM_CREATE || rowAction == ItemLogger.ITEM_SELL || rowAction == ItemLogger.ITEM_BUY) { - inventoryAction = ((rowAction == ItemLogger.ITEM_PICKUP || rowAction == ItemLogger.ITEM_CREATE || rowAction == ItemLogger.ITEM_BUY) ? 1 : 0); - } - else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.ITEM_ADD_ENDER) { - inventoryAction = (rowAction == ItemLogger.ITEM_REMOVE_ENDER ? 1 : 0); - } - else { - inventoryAction = (rowAction == ItemLogger.ITEM_REMOVE ? 1 : 0); - } - - int action = rollbackType == 0 ? (inventoryAction ^ 1) : inventoryAction; - ItemStack itemstack = new ItemStack(inventoryItem, rowAmount); - Object[] populatedStack = populateItemStack(itemstack, rowMetadata); - if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.ITEM_ADD_ENDER) { - modifyContainerItems(containerType, player.getEnderChest(), (Integer) populatedStack[0], ((ItemStack) populatedStack[2]).clone(), action ^ 1); - } - int modifiedArmor = modifyContainerItems(containerType, player.getInventory(), (Integer) populatedStack[0], (ItemStack) populatedStack[2], action); - if (modifiedArmor > -1) { - List currentSortList = sortPlayers.getOrDefault(player, new ArrayList<>()); - if (!currentSortList.contains(modifiedArmor)) { - currentSortList.add(modifiedArmor); - } - sortPlayers.put(player, currentSortList); - } - - itemCount1 = itemCount1 + rowAmount; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); - continue; // remove this for merged rollbacks in future? (be sure to re-enable chunk sorting) - } - - if (inventoryRollback || rowAction > 1) { - continue; // skip inventory & ender chest transactions - } - - if ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1)) { - ItemStack itemstack = new ItemStack(rowType, rowAmount); - Object[] populatedStack = populateItemStack(itemstack, rowMetadata); - String faceData = (String) populatedStack[1]; - - if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) { - container = null; // container patch 2.14.0 - String world = WorldUtils.getWorldName(rowWorldId); - if (world.length() == 0) { - continue; - } - - World bukkitWorld = Bukkit.getServer().getWorld(world); - if (bukkitWorld == null) { - continue; - } - Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (!bukkitWorld.isChunkLoaded(block.getChunk())) { - bukkitWorld.getChunkAt(block.getLocation()); - } - - if (BlockGroup.CONTAINERS.contains(block.getType())) { - BlockState blockState = block.getState(); - if (blockState instanceof Jukebox) { - container = blockState; - } - else { - container = BlockUtils.getContainerInventory(blockState, false); - } - - containerType = block.getType(); - } - else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND) || BlockGroup.CONTAINERS.contains(Material.ITEM_FRAME)) { - for (Entity entity : block.getChunk().getEntities()) { - if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) { - if (entity instanceof ArmorStand) { - container = ItemUtils.getEntityEquipment((LivingEntity) entity); - containerType = Material.ARMOR_STAND; - } - else if (entity instanceof ItemFrame) { - container = entity; - containerType = Material.ITEM_FRAME; - if (faceData.length() > 0 && (BlockFace.valueOf(faceData) == ((ItemFrame) entity).getFacing())) { - break; - } - } - } - } - } - - lastX = rowX; - lastY = rowY; - lastZ = rowZ; - lastWorldId = rowWorldId; - lastFace = faceData; - } - - if (container != null) { - int action = 0; - if (rollbackType == 0 && rowAction == 0) { - action = 1; - } - - if (rollbackType == 1 && rowAction == 1) { - action = 1; - } - - int slot = (Integer) populatedStack[0]; - itemstack = (ItemStack) populatedStack[2]; - - modifyContainerItems(containerType, container, slot, itemstack, action); - itemCount1 = itemCount1 + rowAmount; - } - containerInit = true; - } - } - - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); - } - itemData.clear(); - - for (Entry> sortEntry : sortPlayers.entrySet()) { - sortContainerItems(sortEntry.getKey().getInventory(), sortEntry.getValue()); - } - sortPlayers.clear(); - - int[] rollbackHashData1 = ConfigHandler.rollbackHash.get(finalUserString); - int itemCount1 = rollbackHashData1[0]; - int blockCount1 = rollbackHashData1[1]; - int entityCount1 = rollbackHashData1[2]; - int scannedWorlds = rollbackHashData1[4]; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 1, (scannedWorlds + 1) }); - - // Teleport players out of danger if they're within this chunk - if (preview == 0) { - for (Player player : Bukkit.getOnlinePlayers()) { - Location playerLocation = player.getLocation(); - String playerWorld = playerLocation.getWorld().getName(); - int chunkX = playerLocation.getBlockX() >> 4; - int chunkZ = playerLocation.getBlockZ() >> 4; - - if (bukkitRollbackWorld.getName().equals(playerWorld) && chunkX == finalChunkX && chunkZ == finalChunkZ) { - Teleport.performSafeTeleport(player, playerLocation, false); - } - } - } - } - catch (Exception e) { - e.printStackTrace(); - int[] rollbackHashData1 = ConfigHandler.rollbackHash.get(finalUserString); - int itemCount1 = rollbackHashData1[0]; - int blockCount1 = rollbackHashData1[1]; - int entityCount1 = rollbackHashData1[2]; - int scannedWorlds = rollbackHashData1[4]; - - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 2, (scannedWorlds + 1) }); - } + // Process this chunk using our new RollbackProcessor class + ArrayList blockData = finalBlockList != null ? finalBlockList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); + ArrayList itemData = finalItemList != null ? finalItemList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); + RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, blockData, itemData, rollbackType, preview, finalUserString, finalUser instanceof Player ? (Player) finalUser : null, bukkitRollbackWorld, inventoryRollback); }, chunkLocation, 0); } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java new file mode 100644 index 0000000..9c3ef2a --- /dev/null +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -0,0 +1,593 @@ +package net.coreprotect.database.rollback; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.DyeColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Banner; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.CommandBlock; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.banner.Pattern; +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.Bisected.Half; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.MultipleFacing; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.Bed.Part; +import org.bukkit.block.data.type.Chest; +import org.bukkit.block.data.type.Door; +import org.bukkit.block.data.type.Door.Hinge; +import org.bukkit.block.data.type.Piston; +import org.bukkit.block.data.type.PistonHead; +import org.bukkit.block.data.type.RedstoneWire; +import org.bukkit.block.data.type.Snow; +import org.bukkit.block.data.type.Stairs; +import org.bukkit.block.data.type.TechnicalPiston; +import org.bukkit.block.data.type.TrapDoor; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import net.coreprotect.bukkit.BukkitAdapter; +import net.coreprotect.config.ConfigHandler; +import net.coreprotect.consumer.Queue; +import net.coreprotect.model.BlockGroup; +import net.coreprotect.paper.PaperAdapter; +import net.coreprotect.thread.CacheHandler; +import net.coreprotect.utility.BlockUtils; +import net.coreprotect.utility.ChestTool; +import net.coreprotect.utility.EntityUtils; +import net.coreprotect.utility.ItemUtils; +import net.coreprotect.utility.Util; +import net.coreprotect.utility.entity.HangingUtil; + +public class RollbackBlockHandler extends Queue { + + /** + * Handle block-related rollback operations + * + * @param block + * The block to modify + * @param row + * Block data from the database (used only for specific operations) + * @param rollbackType + * The type of rollback (0=rollback, 1=restore) + * @param clearInventories + * Whether to clear container inventories + * @param chunkChanges + * Map of block changes to apply + * @param countBlock + * Whether to count this block in stats + * @param oldTypeMaterial + * The previous material type + * @param pendingChangeType + * The pending change material type + * @param pendingChangeData + * The pending change block data + * @param finalUserString + * The username for this rollback + * @param rawBlockData + * The raw block data + * @param changeType + * The current block type + * @param changeBlockData + * The current block data + * @param meta + * Block metadata + * @param blockData + * The processed block data + * @param rowUser + * The username associated with this block change + * @param rowType + * The material type for this block change + * @param rowX + * The X coordinate + * @param rowY + * The Y coordinate + * @param rowZ + * The Z coordinate + * @param rowTypeRaw + * The raw type value + * @param rowData + * The data value + * @param rowAction + * The action value + * @param rowWorldId + * The world ID + * @param blockDataString + * The block data as a string + * @return Updated count status + */ + public static boolean processBlockChange(Block block, Object[] row, int rollbackType, boolean clearInventories, Map chunkChanges, boolean countBlock, Material oldTypeMaterial, Material pendingChangeType, BlockData pendingChangeData, String finalUserString, BlockData rawBlockData, Material changeType, BlockData changeBlockData, ArrayList meta, BlockData blockData, String rowUser, Material rowType, int rowX, int rowY, int rowZ, int rowTypeRaw, int rowData, int rowAction, int rowWorldId, String blockDataString) { + + boolean changeBlock = true; + World bukkitWorld = block.getWorld(); + int unixtimestamp = (int) (System.currentTimeMillis() / 1000L); + + try { + if (changeBlock) { + /* If modifying the head of a piston, update the base piston block to prevent it from being destroyed */ + if (changeBlockData instanceof PistonHead) { + PistonHead pistonHead = (PistonHead) changeBlockData; + Block pistonBlock = block.getRelative(pistonHead.getFacing().getOppositeFace()); + BlockData pistonData = pistonBlock.getBlockData(); + if (pistonData instanceof Piston) { + Piston piston = (Piston) pistonData; + piston.setExtended(false); + pistonBlock.setBlockData(piston, false); + } + } + else if (rowType == Material.MOVING_PISTON && blockData instanceof TechnicalPiston && !(blockData instanceof PistonHead)) { + TechnicalPiston technicalPiston = (TechnicalPiston) blockData; + rowType = (technicalPiston.getType() == org.bukkit.block.data.type.TechnicalPiston.Type.STICKY ? Material.STICKY_PISTON : Material.PISTON); + blockData = rowType.createBlockData(); + ((Piston) blockData).setFacing(technicalPiston.getFacing()); + } + + if ((rowType == Material.AIR) && ((BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) || (oldTypeMaterial == Material.PAINTING))) { + HangingUtil.removeHanging(block.getState(), blockDataString); + } + else if ((BukkitAdapter.ADAPTER.isItemFrame(rowType)) || (rowType == Material.PAINTING)) { + HangingUtil.spawnHanging(block.getState(), rowType, blockDataString, rowData); + } + else if ((rowType == Material.ARMOR_STAND)) { + Location location1 = block.getLocation(); + location1.setX(location1.getX() + 0.50); + location1.setZ(location1.getZ() + 0.50); + location1.setYaw(rowData); + boolean exists = false; + + for (Entity entity : block.getChunk().getEntities()) { + if (entity instanceof ArmorStand) { + if (entity.getLocation().getBlockX() == location1.getBlockX() && entity.getLocation().getBlockY() == location1.getBlockY() && entity.getLocation().getBlockZ() == location1.getBlockZ()) { + exists = true; + } + } + } + + if (!exists) { + Entity entity = block.getLocation().getWorld().spawnEntity(location1, EntityType.ARMOR_STAND); + PaperAdapter.ADAPTER.teleportAsync(entity, location1); + } + } + else if ((rowType == Material.END_CRYSTAL)) { + Location location1 = block.getLocation(); + location1.setX(location1.getX() + 0.50); + location1.setZ(location1.getZ() + 0.50); + boolean exists = false; + + for (Entity entity : block.getChunk().getEntities()) { + if (entity instanceof EnderCrystal) { + if (entity.getLocation().getBlockX() == location1.getBlockX() && entity.getLocation().getBlockY() == location1.getBlockY() && entity.getLocation().getBlockZ() == location1.getBlockZ()) { + exists = true; + } + } + } + + if (!exists) { + Entity entity = block.getLocation().getWorld().spawnEntity(location1, BukkitAdapter.ADAPTER.getEntityType(Material.END_CRYSTAL)); + EnderCrystal enderCrystal = (EnderCrystal) entity; + enderCrystal.setShowingBottom((rowData != 0)); + PaperAdapter.ADAPTER.teleportAsync(entity, location1); + } + } + else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.WATER))) { + if (pendingChangeData instanceof Waterlogged) { + Waterlogged waterlogged = (Waterlogged) pendingChangeData; + waterlogged.setWaterlogged(false); + BlockUtils.prepareTypeAndData(chunkChanges, block, null, waterlogged, false); + } + else { + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); + } + + return countBlock; + } + else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.SNOW))) { + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); + return countBlock; + } + else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.END_CRYSTAL))) { + for (Entity entity : block.getChunk().getEntities()) { + if (entity instanceof EnderCrystal) { + if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) { + entity.remove(); + } + } + } + } + else if (rollbackType == 0 && rowAction == 0 && (rowType == Material.AIR)) { + // broke block ID #0 + } + else if ((rowType == Material.AIR) || (rowType == Material.TNT)) { + if (clearInventories) { + if (BlockGroup.CONTAINERS.contains(changeType)) { + Inventory inventory = BlockUtils.getContainerInventory(block.getState(), false); + if (inventory != null) { + inventory.clear(); + } + } + else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND)) { + if ((oldTypeMaterial == Material.ARMOR_STAND)) { + for (Entity entity : block.getChunk().getEntities()) { + if (entity instanceof ArmorStand) { + Location entityLocation = entity.getLocation(); + entityLocation.setY(entityLocation.getY() + 0.99); + + if (entityLocation.getBlockX() == rowX && entityLocation.getBlockY() == rowY && entityLocation.getBlockZ() == rowZ) { + ItemUtils.getEntityEquipment((ArmorStand) entity).clear(); + + entityLocation.setY(entityLocation.getY() - 1.99); + PaperAdapter.ADAPTER.teleportAsync(entity, entityLocation); + entity.remove(); + } + } + } + } + } + } + + boolean remove = true; + if ((rowType == Material.AIR)) { + if (pendingChangeData instanceof Waterlogged) { + Waterlogged waterlogged = (Waterlogged) pendingChangeData; + if (waterlogged.isWaterlogged()) { + BlockUtils.prepareTypeAndData(chunkChanges, block, Material.WATER, Material.WATER.createBlockData(), true); + remove = false; + } + } + else if ((pendingChangeType == Material.WATER)) { + if (rawBlockData instanceof Waterlogged) { + Waterlogged waterlogged = (Waterlogged) rawBlockData; + if (waterlogged.isWaterlogged()) { + remove = false; + } + } + } + } + + if (remove) { + boolean physics = true; + if ((changeType == Material.NETHER_PORTAL) || changeBlockData instanceof MultipleFacing || changeBlockData instanceof Snow || changeBlockData instanceof Stairs || changeBlockData instanceof RedstoneWire || changeBlockData instanceof Chest) { + physics = true; + } + else if (changeBlockData instanceof Bisected && !(changeBlockData instanceof TrapDoor)) { + Bisected bisected = (Bisected) changeBlockData; + Location bisectLocation = block.getLocation().clone(); + if (bisected.getHalf() == Half.TOP) { + bisectLocation.setY(bisectLocation.getY() - 1); + } + else { + bisectLocation.setY(bisectLocation.getY() + 1); + } + + int worldMaxHeight = bukkitWorld.getMaxHeight(); + int worldMinHeight = BukkitAdapter.ADAPTER.getMinHeight(bukkitWorld); + if (bisectLocation.getBlockY() >= worldMinHeight && bisectLocation.getBlockY() < worldMaxHeight) { + Block bisectBlock = block.getWorld().getBlockAt(bisectLocation); + BlockUtils.prepareTypeAndData(chunkChanges, bisectBlock, rowType, null, false); + + if (countBlock) { + updateBlockCount(finalUserString, 1); + } + } + } + else if (changeBlockData instanceof Bed) { + Bed bed = (Bed) changeBlockData; + if (bed.getPart() == Part.FOOT) { + Block adjacentBlock = block.getRelative(bed.getFacing()); + BlockUtils.prepareTypeAndData(chunkChanges, adjacentBlock, rowType, null, false); + } + } + + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, null, physics); + } + + return countBlock; + } + else if ((rowType == Material.SPAWNER)) { + try { + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); + CreatureSpawner mobSpawner = (CreatureSpawner) block.getState(); + mobSpawner.setSpawnedType(EntityUtils.getSpawnerType(rowData)); + mobSpawner.update(); + + return countBlock; + } + catch (Exception e) { + // e.printStackTrace(); + } + } + else if ((rowType == Material.SKELETON_SKULL) || (rowType == Material.SKELETON_WALL_SKULL) || (rowType == Material.WITHER_SKELETON_SKULL) || (rowType == Material.WITHER_SKELETON_WALL_SKULL) || (rowType == Material.ZOMBIE_HEAD) || (rowType == Material.ZOMBIE_WALL_HEAD) || (rowType == Material.PLAYER_HEAD) || (rowType == Material.PLAYER_WALL_HEAD) || (rowType == Material.CREEPER_HEAD) || (rowType == Material.CREEPER_WALL_HEAD) || (rowType == Material.DRAGON_HEAD) || (rowType == Material.DRAGON_WALL_HEAD)) { // skull + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); + if (rowData > 0) { + Queue.queueSkullUpdate(rowUser, block.getState(), rowData); + } + + return countBlock; + } + else if (BukkitAdapter.ADAPTER.isSign(rowType)) {// sign + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); + Queue.queueSignUpdate(rowUser, block.getState(), rollbackType, (Integer) row[1]); + + return countBlock; + } + else if (BlockGroup.SHULKER_BOXES.contains(rowType)) { + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); + if (countBlock) { + updateBlockCount(finalUserString, 1); + } + if (meta != null) { + Inventory inventory = BlockUtils.getContainerInventory(block.getState(), false); + for (Object value : meta) { + ItemStack item = ItemUtils.unserializeItemStackLegacy(value); + if (item != null) { + RollbackUtil.modifyContainerItems(rowType, inventory, 0, item, 1); + } + } + } + return false; + } + else if (rowType == Material.COMMAND_BLOCK || rowType == Material.REPEATING_COMMAND_BLOCK || rowType == Material.CHAIN_COMMAND_BLOCK) { // command block + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); + if (countBlock) { + updateBlockCount(finalUserString, 1); + } + + if (meta != null) { + CommandBlock commandBlock = (CommandBlock) block.getState(); + for (Object value : meta) { + if (value instanceof String) { + String string = (String) value; + commandBlock.setCommand(string); + commandBlock.update(); + } + } + } + return false; + } + else if ((rowType == Material.WATER)) { + if (pendingChangeData instanceof Waterlogged) { + Waterlogged waterlogged = (Waterlogged) pendingChangeData; + waterlogged.setWaterlogged(true); + BlockUtils.prepareTypeAndData(chunkChanges, block, null, waterlogged, false); + } + else { + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); + } + + return countBlock; + } + else if ((rowType == Material.NETHER_PORTAL) && rowAction == 0) { + BlockUtils.prepareTypeAndData(chunkChanges, block, Material.FIRE, null, true); + } + else if (blockData == null && rowData > 0 && (rowType == Material.IRON_DOOR || BlockGroup.DOORS.contains(rowType))) { + if (countBlock) { + updateBlockCount(finalUserString, 1); + } + + block.setType(rowType, false); + Door door = (Door) block.getBlockData(); + if (rowData >= 8) { + door.setHalf(Half.TOP); + rowData = rowData - 8; + } + else { + door.setHalf(Half.BOTTOM); + } + if (rowData >= 4) { + door.setHinge(Hinge.RIGHT); + rowData = rowData - 4; + } + else { + door.setHinge(Hinge.LEFT); + } + BlockFace face = BlockFace.NORTH; + + switch (rowData) { + case 0: + face = BlockFace.EAST; + break; + case 1: + face = BlockFace.SOUTH; + break; + case 2: + face = BlockFace.WEST; + break; + } + + door.setFacing(face); + door.setOpen(false); + block.setBlockData(door, false); + return false; + } + else if (blockData == null && rowData > 0 && (rowType.name().endsWith("_BED"))) { + if (countBlock) { + updateBlockCount(finalUserString, 1); + } + + block.setType(rowType, false); + Bed bed = (Bed) block.getBlockData(); + BlockFace face = BlockFace.NORTH; + + if (rowData > 4) { + bed.setPart(Part.HEAD); + rowData = rowData - 4; + } + + switch (rowData) { + case 2: + face = BlockFace.WEST; + break; + case 3: + face = BlockFace.EAST; + break; + case 4: + face = BlockFace.SOUTH; + break; + } + + bed.setFacing(face); + block.setBlockData(bed, false); + return false; + } + else if (rowType.name().endsWith("_BANNER")) { + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); + if (countBlock) { + updateBlockCount(finalUserString, 1); + } + + if (meta != null) { + Banner banner = (Banner) block.getState(); + + for (Object value : meta) { + if (value instanceof DyeColor) { + banner.setBaseColor((DyeColor) value); + } + else if (value instanceof Map) { + @SuppressWarnings("unchecked") + Pattern pattern = new Pattern((Map) value); + banner.addPattern(pattern); + } + } + + banner.update(); + } + return false; + } + else if (rowType != changeType && (BlockGroup.CONTAINERS.contains(rowType) || BlockGroup.CONTAINERS.contains(changeType))) { + block.setType(Material.AIR); // Clear existing container to prevent errors + + boolean isChest = (blockData instanceof Chest); + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, (isChest)); + if (isChest) { + ChestTool.updateDoubleChest(block, blockData, false); + } + + return countBlock; + } + else if (BlockGroup.UPDATE_STATE.contains(rowType) || rowType.name().contains("CANDLE")) { + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); + ChestTool.updateDoubleChest(block, blockData, true); + return countBlock; + } + else if (rowType != Material.AIR && rawBlockData instanceof Bisected && !(rawBlockData instanceof Stairs || rawBlockData instanceof TrapDoor)) { + Bisected bisected = (Bisected) rawBlockData; + Bisected bisectData = (Bisected) rawBlockData.clone(); + Location bisectLocation = block.getLocation().clone(); + if (bisected.getHalf() == Half.TOP) { + bisectData.setHalf(Half.BOTTOM); + bisectLocation.setY(bisectLocation.getY() - 1); + } + else { + bisectData.setHalf(Half.TOP); + bisectLocation.setY(bisectLocation.getY() + 1); + } + + int worldMaxHeight = bukkitWorld.getMaxHeight(); + int worldMinHeight = BukkitAdapter.ADAPTER.getMinHeight(bukkitWorld); + if (bisectLocation.getBlockY() >= worldMinHeight && bisectLocation.getBlockY() < worldMaxHeight) { + Block bisectBlock = block.getWorld().getBlockAt(bisectLocation); + BlockUtils.prepareTypeAndData(chunkChanges, bisectBlock, rowType, bisectData, false); + } + + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); + if (countBlock) { + updateBlockCount(finalUserString, 2); + } + return false; + } + else if (rowType != Material.AIR && rawBlockData instanceof Bed) { + Bed bed = (Bed) rawBlockData; + if (bed.getPart() == Part.FOOT) { + Block adjacentBlock = block.getRelative(bed.getFacing()); + Bed bedData = (Bed) rawBlockData.clone(); + bedData.setPart(Part.HEAD); + BlockUtils.prepareTypeAndData(chunkChanges, adjacentBlock, rowType, bedData, false); + if (countBlock) { + updateBlockCount(finalUserString, 1); + } + } + + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); + return countBlock; + } + else { + boolean physics = true; + /* + if (blockData instanceof MultipleFacing || BukkitAdapter.ADAPTER.isWall(blockData) || blockData instanceof Snow || blockData instanceof Stairs || blockData instanceof RedstoneWire || blockData instanceof Chest) { + physics = !(blockData instanceof Snow) || block.getY() <= BukkitAdapter.ADAPTER.getMinHeight(block.getWorld()) || (block.getWorld().getBlockAt(block.getX(), block.getY() - 1, block.getZ()).getType().equals(Material.GRASS_BLOCK)); + } + */ + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, physics); + return countBlock; + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + + if ((rowType != Material.AIR) && changeBlock) { + if (rowUser.length() > 0) { + CacheHandler.lookupCache.put(rowX + "." + rowY + "." + rowZ + "." + rowWorldId, new Object[] { unixtimestamp, rowUser, rowType }); + } + } + + return countBlock; + } + + /** + * Update the block count in the rollback hash + * + * @param userString + * The username for this rollback + * @param increment + * The amount to increment the block count by + */ + protected static void updateBlockCount(String userString, int increment) { + int[] rollbackHashData = ConfigHandler.rollbackHash.get(userString); + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; + + blockCount += increment; + ConfigHandler.rollbackHash.put(userString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorlds }); + } + + /** + * Apply all pending block changes to the world + * + * @param chunkChanges + * Map of blocks to change + * @param preview + * Whether this is a preview + * @param user + * The user performing the rollback + */ + public static void applyBlockChanges(Map chunkChanges, int preview, Player user) { + for (Entry chunkChange : chunkChanges.entrySet()) { + Block changeBlock = chunkChange.getKey(); + BlockData changeBlockData = chunkChange.getValue(); + if (preview > 0 && user != null) { + Util.sendBlockChange(user, changeBlock.getLocation(), changeBlockData); + } + else { + BlockUtils.setTypeAndData(changeBlock, null, changeBlockData, true); + } + } + chunkChanges.clear(); + } +} diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java new file mode 100644 index 0000000..64add74 --- /dev/null +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -0,0 +1,202 @@ +package net.coreprotect.database.rollback; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; + +import net.coreprotect.config.ConfigHandler; +import net.coreprotect.thread.CacheHandler; +import net.coreprotect.utility.EntityUtils; +import net.coreprotect.utility.WorldUtils; + +public class RollbackEntityHandler { + + /** + * Processes an entity-related rollback operation. + * + * @param row + * The database row containing entity data (used only for specific operations) + * @param rollbackType + * The type of rollback (0 for rollback, 1 for restore) + * @param finalUserString + * The user string for tracking operations + * @param rowTypeRaw + * The raw type value + * @param rowData + * The data value + * @param rowAction + * The action value + * @param rowRolledBack + * Whether the entity was already rolled back + * @param rowX + * The X coordinate + * @param rowY + * The Y coordinate + * @param rowZ + * The Z coordinate + * @param rowWorldId + * The world ID + * @param rowUserId + * The user ID + * @param rowUser + * The username associated with this entity change + * @return The number of entities affected (1 if successful, 0 otherwise) + */ + public static int processEntity(Object[] row, int rollbackType, String finalUserString, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser) { + try { + // Entity kill + if (rowAction == 3) { + String world = getWorldName(rowWorldId); + if (world.isEmpty()) { + return 0; + } + + World bukkitWorld = Bukkit.getServer().getWorld(world); + if (bukkitWorld == null) { + return 0; + } + + Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); + if (!bukkitWorld.isChunkLoaded(block.getChunk())) { + bukkitWorld.getChunkAt(block.getLocation()); + } + + if (rowTypeRaw > 0) { + // Spawn in entity + if (rowRolledBack == 0) { + EntityType entityType = EntityUtils.getEntityType(rowTypeRaw); + // Use the spawnEntity method from the RollbackUtil class instead of Queue + spawnEntity(rowUser, block.getState(), entityType, rowData); + updateEntityCount(finalUserString, 1); + return 1; + } + } + else if (rowTypeRaw <= 0) { + int oldTypeRaw = rowTypeRaw; + // Attempt to remove entity + if (rowRolledBack == 1) { + boolean removed = false; + int entityId = -1; + String entityName = EntityUtils.getEntityType(Math.abs(oldTypeRaw)).name(); + String token = "" + rowX + "." + rowY + "." + rowZ + "." + rowWorldId + "." + entityName + ""; + Object[] cachedEntity = CacheHandler.entityCache.get(token); + + if (cachedEntity != null) { + entityId = (Integer) cachedEntity[1]; + } + + int xmin = rowX - 5; + int xmax = rowX + 5; + int ymin = rowY - 1; + int ymax = rowY + 1; + int zmin = rowZ - 5; + int zmax = rowZ + 5; + + for (Entity entity : block.getChunk().getEntities()) { + if (entityId > -1) { + int id = entity.getEntityId(); + if (id == entityId) { + updateEntityCount(finalUserString, 1); + removed = true; + entity.remove(); + break; + } + } + else { + if (entity.getType().equals(EntityUtils.getEntityType(Math.abs(oldTypeRaw)))) { + Location entityLocation = entity.getLocation(); + int entityx = entityLocation.getBlockX(); + int entityY = entityLocation.getBlockY(); + int entityZ = entityLocation.getBlockZ(); + + if (entityx >= xmin && entityx <= xmax && entityY >= ymin && entityY <= ymax && entityZ >= zmin && entityZ <= zmax) { + updateEntityCount(finalUserString, 1); + removed = true; + entity.remove(); + break; + } + } + } + } + + if (!removed && entityId > -1) { + for (Entity entity : block.getWorld().getLivingEntities()) { + int id = entity.getEntityId(); + if (id == entityId) { + updateEntityCount(finalUserString, 1); + removed = true; + entity.remove(); + break; + } + } + } + + if (removed) { + return 1; + } + } + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + + return 0; + } + + /** + * Gets the world name from a world ID. + * + * @param worldId + * The world ID + * @return The world name + */ + private static String getWorldName(int worldId) { + return WorldUtils.getWorldName(worldId); + } + + /** + * Updates the entity count in the rollback hash for a specific user. + * + * @param userString + * The user string identifier + * @param increment + * The amount to increment the entity count by + */ + public static void updateEntityCount(String userString, int increment) { + int[] rollbackHashData = ConfigHandler.rollbackHash.get(userString); + if (rollbackHashData != null) { + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int next = rollbackHashData[3]; + int scannedWorlds = rollbackHashData[4]; + + entityCount += increment; + + ConfigHandler.rollbackHash.put(userString, new int[] { itemCount, blockCount, entityCount, next, scannedWorlds }); + } + } + + /** + * Spawns an entity at the given block location. + * + * @param user + * The username of the player + * @param block + * The block state where the entity should be spawned + * @param type + * The type of entity to spawn + * @param data + * Additional data for the entity + */ + public static void spawnEntity(String user, BlockState block, EntityType type, int data) { + // Create a new helper method that will delegate to Queue + RollbackUtil.queueEntitySpawn(user, block, type, data); + } +} diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackItemHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackItemHandler.java new file mode 100644 index 0000000..5e50022 --- /dev/null +++ b/src/main/java/net/coreprotect/database/rollback/RollbackItemHandler.java @@ -0,0 +1,127 @@ +package net.coreprotect.database.rollback; + +import java.io.ByteArrayInputStream; +import java.util.List; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.util.io.BukkitObjectInputStream; + +import net.coreprotect.config.ConfigHandler; + +public class RollbackItemHandler { + + /** + * Populates an ItemStack with metadata from the database + * + * @param itemstack + * The ItemStack to populate + * @param metadata + * The metadata as a byte array + * @return Object array containing [slot, facing, itemstack] + */ + public static Object[] populateItemStack(ItemStack itemstack, byte[] metadata) { + int slot = 0; + String face = ""; + + if (metadata != null) { + try { + ByteArrayInputStream metaByteStream = new ByteArrayInputStream(metadata); + BukkitObjectInputStream metaObjectStream = new BukkitObjectInputStream(metaByteStream); + @SuppressWarnings("unchecked") + List meta = (List) metaObjectStream.readObject(); + metaObjectStream.close(); + metaByteStream.close(); + + for (Object value : meta) { + if (value instanceof Integer) { + slot = (Integer) value; + } + else if (value instanceof ItemStack) { + itemstack = (ItemStack) value; + } + else if (value instanceof String) { + face = (String) value; + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + + return new Object[] { slot, face, itemstack }; + } + + /** + * Sorts inventory items for better display + * + * @param inventory + * The inventory to sort + * @param slots + * The slots to sort + */ + public static void sortContainerItems(PlayerInventory inventory, List slots) { + if (slots.contains(0)) { + ItemStack boots = inventory.getBoots(); + if (boots != null && !boots.getType().equals(Material.AIR)) { + if (!boots.getType().name().contains("BOOTS")) { + inventory.setBoots(new ItemStack(Material.AIR)); + inventory.addItem(boots); + } + } + } + + if (slots.contains(1)) { + ItemStack leggings = inventory.getLeggings(); + if (leggings != null && !leggings.getType().equals(Material.AIR)) { + if (!leggings.getType().name().contains("LEGGINGS")) { + inventory.setLeggings(new ItemStack(Material.AIR)); + inventory.addItem(leggings); + } + } + } + + if (slots.contains(2)) { + ItemStack chestplate = inventory.getChestplate(); + if (chestplate != null && !chestplate.getType().equals(Material.AIR)) { + if (!chestplate.getType().name().contains("CHESTPLATE")) { + inventory.setChestplate(new ItemStack(Material.AIR)); + inventory.addItem(chestplate); + } + } + } + + if (slots.contains(3)) { + ItemStack helmet = inventory.getHelmet(); + if (helmet != null && !helmet.getType().equals(Material.AIR)) { + String materialName = helmet.getType().name(); + if (!materialName.contains("HELMET") && !materialName.contains("SKULL") && !materialName.endsWith("_HEAD")) { + inventory.setHelmet(new ItemStack(Material.AIR)); + inventory.addItem(helmet); + } + } + } + } + + /** + * Update the item count in the rollback hash + * + * @param userString + * The username for this rollback + * @param increment + * The amount to increment the item count by + */ + public static void updateItemCount(String userString, int increment) { + int[] rollbackHashData = ConfigHandler.rollbackHash.get(userString); + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; + + itemCount += increment; + ConfigHandler.rollbackHash.put(userString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorlds }); + } + +} diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java new file mode 100644 index 0000000..20ab6eb --- /dev/null +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -0,0 +1,454 @@ +package net.coreprotect.database.rollback; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Jukebox; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import net.coreprotect.bukkit.BukkitAdapter; +import net.coreprotect.config.Config; +import net.coreprotect.config.ConfigHandler; +import net.coreprotect.database.logger.ItemLogger; +import net.coreprotect.model.BlockGroup; +import net.coreprotect.utility.BlockUtils; +import net.coreprotect.utility.ItemUtils; +import net.coreprotect.utility.MaterialUtils; +import net.coreprotect.utility.Teleport; +import net.coreprotect.utility.WorldUtils; + +public class RollbackProcessor { + + /** + * Process data for a specific chunk + * + * @param finalChunkX + * The chunk X coordinate + * @param finalChunkZ + * The chunk Z coordinate + * @param chunkKey + * The chunk lookup key + * @param blockList + * The list of block data to process + * @param itemList + * The list of item data to process + * @param rollbackType + * The rollback type (0=rollback, 1=restore) + * @param preview + * Whether this is a preview (0=no, 1=yes-non-destructive, 2=yes-destructive) + * @param finalUserString + * The username performing the rollback + * @param finalUser + * The user performing the rollback + * @param bukkitRollbackWorld + * The world to process + * @return True if successful, false if there was an error + */ + public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { + try { + boolean clearInventories = Config.getGlobal().ROLLBACK_ITEMS; + ArrayList data = blockList != null ? blockList : new ArrayList<>(); + ArrayList itemData = itemList != null ? itemList : new ArrayList<>(); + Map chunkChanges = new LinkedHashMap<>(); + + // Process blocks + for (Object[] row : data) { + int unixtimestamp = (int) (System.currentTimeMillis() / 1000L); + int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; + + int rowX = (Integer) row[3]; + int rowY = (Integer) row[4]; + int rowZ = (Integer) row[5]; + int rowTypeRaw = (Integer) row[6]; + int rowData = (Integer) row[7]; + int rowAction = (Integer) row[8]; + int rowRolledBack = MaterialUtils.rolledBack((Integer) row[9], false); + int rowWorldId = (Integer) row[10]; + byte[] rowMeta = (byte[]) row[12]; + byte[] rowBlockData = (byte[]) row[13]; + String blockDataString = BlockUtils.byteDataToString(rowBlockData, rowTypeRaw); + Material rowType = MaterialUtils.getType(rowTypeRaw); + + List meta = null; + if (rowMeta != null) { + meta = Rollback.deserializeMetadata(rowMeta); + } + + BlockData blockData = null; + if (blockDataString != null && blockDataString.contains(":")) { + try { + blockData = Bukkit.getServer().createBlockData(blockDataString); + } + catch (Exception e) { + // corrupt BlockData, let the server automatically set the BlockData instead + } + } + + BlockData rawBlockData = null; + if (blockData != null) { + rawBlockData = blockData.clone(); + } + if (rawBlockData == null && rowType != null && rowType.isBlock()) { + rawBlockData = BlockUtils.createBlockData(rowType); + } + + String rowUser = ConfigHandler.playerIdCacheReversed.get((Integer) row[2]); + int oldTypeRaw = rowTypeRaw; + Material oldTypeMaterial = MaterialUtils.getType(oldTypeRaw); + + if (rowAction == 1 && rollbackType == 0) { // block placement + rowType = Material.AIR; + blockData = null; + rowTypeRaw = 0; + } + else if (rowAction == 0 && rollbackType == 1) { // block removal + rowType = Material.AIR; + blockData = null; + rowTypeRaw = 0; + } + else if (rowAction == 4 && rollbackType == 0) { // entity placement + rowType = null; + rowTypeRaw = 0; + } + else if (rowAction == 3 && rollbackType == 1) { // entity removal + rowType = null; + rowTypeRaw = 0; + } + if (preview > 0) { + if (rowAction != 3) { // entity kill + String world = WorldUtils.getWorldName(rowWorldId); + if (world.length() == 0) { + continue; + } + + World bukkitWorld = Bukkit.getServer().getWorld(world); + if (bukkitWorld == null) { + continue; + } + + Block block = new Location(bukkitWorld, rowX, rowY, rowZ).getBlock(); + if (preview == 2) { + Material blockType = block.getType(); + if (!BukkitAdapter.ADAPTER.isItemFrame(blockType) && !blockType.equals(Material.PAINTING) && !blockType.equals(Material.ARMOR_STAND) && !blockType.equals(Material.END_CRYSTAL)) { + BlockUtils.prepareTypeAndData(chunkChanges, block, blockType, block.getBlockData(), true); + blockCount++; + } + } + else { + if ((!BukkitAdapter.ADAPTER.isItemFrame(rowType)) && (rowType != Material.PAINTING) && (rowType != Material.ARMOR_STAND) && (rowType != Material.END_CRYSTAL)) { + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); + blockCount++; + } + } + } + else { + entityCount++; + } + } + else if (rowAction == 3) { // entity kill + entityCount += RollbackEntityHandler.processEntity(row, rollbackType, finalUserString, rowTypeRaw, rowData, rowAction, MaterialUtils.rolledBack((Integer) row[9], false), rowX, rowY, rowZ, rowWorldId, (Integer) row[2], rowUser); + } + else { + String world = WorldUtils.getWorldName(rowWorldId); + if (world.length() == 0) { + continue; + } + + World bukkitWorld = Bukkit.getServer().getWorld(world); + if (bukkitWorld == null) { + continue; + } + + Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); + if (!bukkitWorld.isChunkLoaded(block.getChunk())) { + bukkitWorld.getChunkAt(block.getLocation()); + } + + boolean changeBlock = true; + boolean countBlock = true; + Material changeType = block.getType(); + BlockData changeBlockData = block.getBlockData(); + BlockData pendingChangeData = chunkChanges.get(block); + Material pendingChangeType = changeType; + + if (pendingChangeData != null) { + pendingChangeType = pendingChangeData.getMaterial(); + } + else { + pendingChangeData = changeBlockData; + } + + if (rowRolledBack == 1 && rollbackType == 0) { // rollback + countBlock = false; + } + + if ((rowType == pendingChangeType) && ((!BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { + // block is already changed! + 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 + changeBlock = false; + } + } + } + else if (rowType == Material.AIR) { + changeBlock = false; + } + + countBlock = false; + } + else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.CAVE_AIR)) { + countBlock = true; + } + + if ((pendingChangeType == Material.WATER) && (rowType != Material.AIR) && (rowType != Material.CAVE_AIR) && blockData != null) { + if (blockData instanceof org.bukkit.block.data.Waterlogged) { + if (Material.WATER.createBlockData().equals(block.getBlockData())) { + org.bukkit.block.data.Waterlogged waterlogged = (org.bukkit.block.data.Waterlogged) blockData; + waterlogged.setWaterlogged(true); + } + } + } + + if (countBlock && RollbackBlockHandler.processBlockChange(block, row, rollbackType, clearInventories, chunkChanges, countBlock, oldTypeMaterial, pendingChangeType, pendingChangeData, finalUserString, rawBlockData, changeType, changeBlockData, meta != null ? new ArrayList<>(meta) : null, blockData, rowUser, rowType, rowX, rowY, rowZ, rowTypeRaw, rowData, rowAction, rowWorldId, BlockUtils.byteDataToString((byte[]) row[13], rowTypeRaw))) { + blockCount++; + } + } + + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorlds }); + } + data.clear(); + + // Apply cached block changes + RollbackBlockHandler.applyBlockChanges(chunkChanges, preview, finalUser instanceof Player ? (Player) finalUser : null); + + // Process container items + Map> sortPlayers = new HashMap<>(); + Object container = null; + Material containerType = null; + boolean containerInit = false; + int lastX = 0; + int lastY = 0; + int lastZ = 0; + int lastWorldId = 0; + String lastFace = ""; + + for (Object[] row : itemData) { + int[] rollbackHashData1 = ConfigHandler.rollbackHash.get(finalUserString); + int itemCount1 = rollbackHashData1[0]; + int blockCount1 = rollbackHashData1[1]; + int entityCount1 = rollbackHashData1[2]; + int scannedWorlds = rollbackHashData1[4]; + int rowX = (Integer) row[3]; + int rowY = (Integer) row[4]; + int rowZ = (Integer) row[5]; + int rowTypeRaw = (Integer) row[6]; + int rowData = (Integer) row[7]; + int rowAction = (Integer) row[8]; + int rowRolledBack = MaterialUtils.rolledBack((Integer) row[9], false); + int rowWorldId = (Integer) row[10]; + int rowAmount = (Integer) row[11]; + byte[] rowMetadata = (byte[]) row[12]; + Material rowType = MaterialUtils.getType(rowTypeRaw); + + int rolledBackInventory = MaterialUtils.rolledBack((Integer) row[9], true); + if (rowType != null) { + if (inventoryRollback && ((rollbackType == 0 && rolledBackInventory == 0) || (rollbackType == 1 && rolledBackInventory == 1))) { + Material inventoryItem = ItemUtils.itemFilter(rowType, ((Integer) row[14] == 0)); + int rowUserId = (Integer) row[2]; + String rowUser = ConfigHandler.playerIdCacheReversed.get(rowUserId); + if (rowUser == null) { + continue; + } + + String uuid = ConfigHandler.uuidCache.get(rowUser.toLowerCase(Locale.ROOT)); + if (uuid == null) { + continue; + } + + Player player = Bukkit.getServer().getPlayer(UUID.fromString(uuid)); + if (player == null) { + continue; + } + + int inventoryAction = 0; + if (rowAction == ItemLogger.ITEM_DROP || rowAction == ItemLogger.ITEM_PICKUP || rowAction == ItemLogger.ITEM_THROW || rowAction == ItemLogger.ITEM_SHOOT || rowAction == ItemLogger.ITEM_BREAK || rowAction == ItemLogger.ITEM_DESTROY || rowAction == ItemLogger.ITEM_CREATE || rowAction == ItemLogger.ITEM_SELL || rowAction == ItemLogger.ITEM_BUY) { + inventoryAction = ((rowAction == ItemLogger.ITEM_PICKUP || rowAction == ItemLogger.ITEM_CREATE || rowAction == ItemLogger.ITEM_BUY) ? 1 : 0); + } + else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.ITEM_ADD_ENDER) { + inventoryAction = (rowAction == ItemLogger.ITEM_REMOVE_ENDER ? 1 : 0); + } + else { + inventoryAction = (rowAction == ItemLogger.ITEM_REMOVE ? 1 : 0); + } + + int action = rollbackType == 0 ? (inventoryAction ^ 1) : inventoryAction; + ItemStack itemstack = new ItemStack(inventoryItem, rowAmount); + Object[] populatedStack = RollbackItemHandler.populateItemStack(itemstack, rowMetadata); + if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.ITEM_ADD_ENDER) { + RollbackUtil.modifyContainerItems(containerType, player.getEnderChest(), (Integer) populatedStack[0], ((ItemStack) populatedStack[2]).clone(), action ^ 1); + } + int modifiedArmor = RollbackUtil.modifyContainerItems(containerType, player.getInventory(), (Integer) populatedStack[0], (ItemStack) populatedStack[2], action); + if (modifiedArmor > -1) { + List currentSortList = sortPlayers.getOrDefault(player, new ArrayList<>()); + if (!currentSortList.contains(modifiedArmor)) { + currentSortList.add(modifiedArmor); + } + sortPlayers.put(player, currentSortList); + } + + itemCount1 = itemCount1 + rowAmount; + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); + continue; // remove this for merged rollbacks in future? (be sure to re-enable chunk sorting) + } + + if (inventoryRollback || rowAction > 1) { + continue; // skip inventory & ender chest transactions + } + + if ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1)) { + ItemStack itemstack = new ItemStack(rowType, rowAmount); + Object[] populatedStack = RollbackItemHandler.populateItemStack(itemstack, rowMetadata); + String faceData = (String) populatedStack[1]; + + if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) { + container = null; // container patch 2.14.0 + String world = WorldUtils.getWorldName(rowWorldId); + if (world.length() == 0) { + continue; + } + + World bukkitWorld = Bukkit.getServer().getWorld(world); + if (bukkitWorld == null) { + continue; + } + Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); + if (!bukkitWorld.isChunkLoaded(block.getChunk())) { + bukkitWorld.getChunkAt(block.getLocation()); + } + + if (BlockGroup.CONTAINERS.contains(block.getType())) { + BlockState blockState = block.getState(); + if (blockState instanceof Jukebox) { + container = blockState; + } + else { + container = BlockUtils.getContainerInventory(blockState, false); + } + + containerType = block.getType(); + } + else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND) || BlockGroup.CONTAINERS.contains(Material.ITEM_FRAME)) { + for (Entity entity : block.getChunk().getEntities()) { + if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) { + if (entity instanceof ArmorStand) { + container = ItemUtils.getEntityEquipment((LivingEntity) entity); + containerType = Material.ARMOR_STAND; + } + else if (entity instanceof ItemFrame) { + container = entity; + containerType = Material.ITEM_FRAME; + if (faceData.length() > 0 && (BlockFace.valueOf(faceData) == ((ItemFrame) entity).getFacing())) { + break; + } + } + } + } + } + + lastX = rowX; + lastY = rowY; + lastZ = rowZ; + lastWorldId = rowWorldId; + lastFace = faceData; + } + + if (container != null) { + int action = 0; + if (rollbackType == 0 && rowAction == 0) { + action = 1; + } + + if (rollbackType == 1 && rowAction == 1) { + action = 1; + } + + int slot = (Integer) populatedStack[0]; + itemstack = (ItemStack) populatedStack[2]; + + RollbackUtil.modifyContainerItems(containerType, container, slot, itemstack, action); + itemCount1 = itemCount1 + rowAmount; + } + containerInit = true; + } + } + + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); + } + itemData.clear(); + + for (Entry> sortEntry : sortPlayers.entrySet()) { + RollbackItemHandler.sortContainerItems(sortEntry.getKey().getInventory(), sortEntry.getValue()); + } + sortPlayers.clear(); + + int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 1, (scannedWorlds + 1) }); + + // Teleport players out of danger if they're within this chunk + if (preview == 0) { + for (Player player : Bukkit.getOnlinePlayers()) { + Location playerLocation = player.getLocation(); + String playerWorld = playerLocation.getWorld().getName(); + int chunkX = playerLocation.getBlockX() >> 4; + int chunkZ = playerLocation.getBlockZ() >> 4; + + if (bukkitRollbackWorld.getName().equals(playerWorld) && chunkX == finalChunkX && chunkZ == finalChunkZ) { + Teleport.performSafeTeleport(player, playerLocation, false); + } + } + } + + return true; + } + catch (Exception e) { + e.printStackTrace(); + int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; + + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) }); + return false; + } + } +} diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java b/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java index c5a1849..fe94662 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java @@ -9,10 +9,12 @@ import org.bukkit.FireworkEffect.Builder; import org.bukkit.Material; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeModifier; +import org.bukkit.block.BlockState; import org.bukkit.block.Jukebox; import org.bukkit.block.ShulkerBox; import org.bukkit.block.banner.Pattern; import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.EntityType; import org.bukkit.entity.ItemFrame; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.Inventory; @@ -32,6 +34,7 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.util.io.BukkitObjectInputStream; import net.coreprotect.bukkit.BukkitAdapter; +import net.coreprotect.consumer.Queue; import net.coreprotect.database.Lookup; import net.coreprotect.model.BlockGroup; import net.coreprotect.utility.ItemUtils; @@ -490,4 +493,103 @@ public class RollbackUtil extends Lookup { return new Object[] { 0, "", itemstack }; } + /** + * Deserializes metadata from a byte array into a list of objects. + * + * @param metadata + * The byte array containing serialized metadata + * @return The deserialized list of objects or null if deserialization fails + */ + public static List deserializeMetadata(byte[] metadata) { + if (metadata == null) { + return null; + } + + try { + ByteArrayInputStream metaByteStream = new ByteArrayInputStream(metadata); + BukkitObjectInputStream metaObjectStream = new BukkitObjectInputStream(metaByteStream); + @SuppressWarnings("unchecked") + List metaList = (List) metaObjectStream.readObject(); + metaObjectStream.close(); + metaByteStream.close(); + return metaList; + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Queues an entity spawn operation for processing. + * + * @param user + * The username of the player + * @param block + * The block state where the entity should be spawned + * @param type + * The type of entity to spawn + * @param data + * Additional data for the entity + */ + public static void queueEntitySpawn(String user, BlockState block, EntityType type, int data) { + if (Queue.class.getDeclaredMethods() != null) { + try { + java.lang.reflect.Method method = Queue.class.getDeclaredMethod("queueEntitySpawn", String.class, BlockState.class, EntityType.class, int.class); + method.setAccessible(true); + method.invoke(null, user, block, type, data); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * Queues a skull update operation for processing. + * + * @param user + * The username of the player + * @param block + * The block state to update + * @param rowId + * The row ID for the skull data + */ + public static void queueSkullUpdate(String user, BlockState block, int rowId) { + if (Queue.class.getDeclaredMethods() != null) { + try { + java.lang.reflect.Method method = Queue.class.getDeclaredMethod("queueSkullUpdate", String.class, BlockState.class, int.class); + method.setAccessible(true); + method.invoke(null, user, block, rowId); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * Queues a sign update operation for processing. + * + * @param user + * The username of the player + * @param block + * The block state to update + * @param action + * The action type + * @param time + * The time of the update + */ + public static void queueSignUpdate(String user, BlockState block, int action, int time) { + if (Queue.class.getDeclaredMethods() != null) { + try { + java.lang.reflect.Method method = Queue.class.getDeclaredMethod("queueSignUpdate", String.class, BlockState.class, int.class, int.class); + method.setAccessible(true); + method.invoke(null, user, block, action, time); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } }