Compare commits

..

1 Commits

Author SHA1 Message Date
SirYwell
5b3c7a2934 fix: missing fallback if current plot is not explicitly overridden 2025-10-18 09:30:54 +02:00
11 changed files with 332 additions and 509 deletions

View File

@@ -80,7 +80,7 @@ tasks.named<ShadowJar>("shadowJar") {
relocate("net.kyori.examination", "com.plotsquared.core.configuration.examination")
relocate("io.papermc.lib", "com.plotsquared.bukkit.paperlib")
relocate("org.bstats", "com.plotsquared.metrics")
relocate("org.enginehub.squirrelid", "com.plotsquared.squirrelid")
relocate("org.enginehub", "com.plotsquared.squirrelid")
relocate("org.khelekore.prtree", "com.plotsquared.prtree")
relocate("com.google.inject", "com.plotsquared.google")
relocate("org.aopalliance", "com.plotsquared.core.aopalliance")

View File

@@ -90,7 +90,12 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap
this.plotGenerator = generator;
this.platformGenerator = this;
this.populators = new ArrayList<>();
this.populators.add(new BlockStatePopulator(this.plotGenerator));
int minecraftMinorVersion = PlotSquared.platform().serverVersion()[1];
if (minecraftMinorVersion >= 17) {
this.populators.add(new BlockStatePopulator(this.plotGenerator));
} else {
this.populators.add(new LegacyBlockStatePopulator(this.plotGenerator));
}
this.full = true;
this.useNewGenerationMethods = PlotSquared.platform().serverVersion()[1] >= 19;
this.biomeProvider = new BukkitPlotBiomeProvider();

View File

@@ -36,7 +36,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Random;
@Deprecated(since = "TODO")
final class LegacyBlockStatePopulator extends BlockPopulator {
private final IndependentPlotGenerator plotGenerator;

View File

@@ -52,7 +52,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Consumer;
public class BukkitQueueCoordinator extends BasicQueueCoordinator {
@@ -211,13 +210,8 @@ public class BukkitQueueCoordinator extends BasicQueueCoordinator {
BaseBlock block = getWorld().getBlock(blockVector3).toBaseBlock(tag);
getWorld().setBlock(blockVector3, block, getSideEffectSet(SideEffectState.NONE));
} catch (WorldEditException ignored) {
StateWrapper.INSTANCE.restore(
getWorld().getName(),
blockVector3.getX(),
blockVector3.getY(),
blockVector3.getZ(),
tag
);
StateWrapper sw = new StateWrapper(tag);
sw.restoreTag(getWorld().getName(), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ());
}
});
}
@@ -301,7 +295,9 @@ public class BukkitQueueCoordinator extends BasicQueueCoordinator {
existing.setBlockData(blockData, false);
if (block.hasNbtData()) {
CompoundTag tag = block.getNbtData();
StateWrapper.INSTANCE.restore(existing, Objects.requireNonNull(tag));
StateWrapper sw = new StateWrapper(tag);
sw.restoreTag(existing);
}
}
}

View File

@@ -34,8 +34,6 @@ import org.bukkit.entity.EntityType;
import org.bukkit.generator.LimitedRegion;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Objects;
/**
* Wraps a {@link LimitedRegion} inside a {@link com.plotsquared.core.queue.QueueCoordinator} so it can be written to.
*
@@ -46,6 +44,7 @@ public class LimitedRegionWrapperQueue extends DelegateQueueCoordinator {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + LimitedRegionWrapperQueue.class.getSimpleName());
private final LimitedRegion limitedRegion;
private boolean useOtherRestoreTagMethod = false;
/**
* @since 6.9.0
@@ -65,11 +64,20 @@ public class LimitedRegionWrapperQueue extends DelegateQueueCoordinator {
boolean result = setBlock(x, y, z, id.toImmutableState());
if (result && id.hasNbtData()) {
CompoundTag tag = id.getNbtData();
StateWrapper sw = new StateWrapper(tag);
try {
StateWrapper.INSTANCE.restore(limitedRegion.getBlockState(x, y, z).getBlock(), Objects.requireNonNull(tag));
if (useOtherRestoreTagMethod && getWorld() != null) {
sw.restoreTag(getWorld().getName(), x, y, z);
} else {
sw.restoreTag(limitedRegion.getBlockState(x, y, z).getBlock());
}
} catch (IllegalArgumentException e) {
LOGGER.error("Error attempting to populate tile entity into the world at location {},{},{}", x, y, z, e);
return false;
} catch (IllegalStateException e) {
useOtherRestoreTagMethod = true;
LOGGER.warn("IllegalStateException attempting to populate tile entity into the world at location {},{},{}. " +
"Possibly on <=1.17.1, switching to secondary method.", x, y, z, e);
}
}
return result;
@@ -105,8 +113,9 @@ public class LimitedRegionWrapperQueue extends DelegateQueueCoordinator {
@Override
public boolean setTile(final int x, final int y, final int z, @NonNull final CompoundTag tag) {
StateWrapper sw = new StateWrapper(tag);
try {
return StateWrapper.INSTANCE.restore(limitedRegion.getBlockState(x, y, z).getBlock(), tag);
return sw.restoreTag(limitedRegion.getBlockState(x, y, z).getBlock());
} catch (IllegalArgumentException e) {
LOGGER.error("Error attempting to populate tile entity into the world at location {},{},{}", x, y, z, e);
return false;

View File

@@ -27,8 +27,6 @@ import com.plotsquared.core.util.WorldUtil;
import com.sk89q.jnbt.CompoundTag;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Objects;
/**
* Schematic Handler.
*/
@@ -41,8 +39,8 @@ public class BukkitSchematicHandler extends SchematicHandler {
}
@Override
public boolean restoreTile(QueueCoordinator queue, CompoundTag tag, int x, int y, int z) {
return StateWrapper.INSTANCE.restore(Objects.requireNonNull(queue.getWorld()).getName(), x, y, z, tag);
public boolean restoreTile(QueueCoordinator queue, CompoundTag ct, int x, int y, int z) {
return new StateWrapper(ct).restoreTag(queue.getWorld().getName(), x, y, z);
}
}

View File

@@ -1,80 +0,0 @@
/*
* PlotSquared, a land and world management plugin for Minecraft.
* Copyright (C) IntellectualSites <https://intellectualsites.com>
* Copyright (C) IntellectualSites team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.bukkit.schematic;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.IntArrayTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.LongArrayTag;
import com.sk89q.jnbt.Tag;
import java.lang.reflect.Type;
final class NbtGsonSerializer implements JsonSerializer<Tag> {
@Override
public JsonElement serialize(final Tag src, final Type typeOfSrc, final JsonSerializationContext context) {
if (src instanceof CompoundTag compoundTag) {
JsonObject object = new JsonObject();
compoundTag.getValue().forEach((s, tag) -> object.add(s, context.serialize(tag)));
return object;
}
if (src instanceof ListTag listTag) {
JsonArray array = new JsonArray();
listTag.getValue().forEach(tag -> array.add(context.serialize(tag)));
return array;
}
if (src instanceof ByteArrayTag byteArrayTag) {
JsonArray array = new JsonArray();
for (final byte b : byteArrayTag.getValue()) {
array.add(b);
}
return array;
}
if (src instanceof IntArrayTag intArrayTag) {
JsonArray array = new JsonArray();
for (final int i : intArrayTag.getValue()) {
array.add(i);
}
return array;
}
if (src instanceof LongArrayTag longArrayTag) {
JsonArray array = new JsonArray();
for (final long l : longArrayTag.getValue()) {
array.add(l);
}
return array;
}
if (src.getValue() instanceof Number number) {
return new JsonPrimitive(number);
}
if (src.getValue() instanceof String string) {
return new JsonPrimitive(string);
}
throw new IllegalArgumentException("Don't know how to serialize " + src);
}
}

View File

@@ -18,71 +18,332 @@
*/
package com.plotsquared.bukkit.schematic;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.destroystokyo.paper.profile.ProfileProperty;
import com.plotsquared.bukkit.util.BukkitUtil;
import com.plotsquared.core.PlotSquared;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.world.item.ItemType;
import io.papermc.lib.PaperLib;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.DyeColor;
import org.bukkit.World;
import org.bukkit.block.Banner;
import org.bukkit.block.Block;
import org.bukkit.block.Container;
import org.bukkit.block.Sign;
import org.bukkit.block.Skull;
import org.bukkit.block.banner.Pattern;
import org.bukkit.block.banner.PatternType;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public sealed interface StateWrapper permits StateWrapperSpigot, StateWrapper.Factory.NoopStateWrapper {
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.UUID;
StateWrapper INSTANCE = Factory.createStateWrapper();
public class StateWrapper {
boolean restore(final @NonNull Block block, final @NonNull CompoundTag data);
public CompoundTag tag;
default boolean restore(final String worldName, final int x, final int y, final int z, final CompoundTag data) {
final World world = BukkitUtil.getWorld(worldName);
private boolean paperErrorTextureSent = false;
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapper.class.getSimpleName());
public StateWrapper(CompoundTag tag) {
this.tag = tag;
}
public static String jsonToColourCode(String str) {
str = str.replace("{\"extra\":", "").replace("],\"text\":\"\"}", "]")
.replace("[{\"color\":\"black\",\"text\":\"", "&0")
.replace("[{\"color\":\"dark_blue\",\"text\":\"", "&1")
.replace("[{\"color\":\"dark_green\",\"text\":\"", "&2")
.replace("[{\"color\":\"dark_aqua\",\"text\":\"", "&3")
.replace("[{\"color\":\"dark_red\",\"text\":\"", "&4")
.replace("[{\"color\":\"dark_purple\",\"text\":\"", "&5")
.replace("[{\"color\":\"gold\",\"text\":\"", "&6")
.replace("[{\"color\":\"gray\",\"text\":\"", "&7")
.replace("[{\"color\":\"dark_gray\",\"text\":\"", "&8")
.replace("[{\"color\":\"blue\",\"text\":\"", "&9")
.replace("[{\"color\":\"green\",\"text\":\"", "&a")
.replace("[{\"color\":\"aqua\",\"text\":\"", "&b")
.replace("[{\"color\":\"red\",\"text\":\"", "&c")
.replace("[{\"color\":\"light_purple\",\"text\":\"", "&d")
.replace("[{\"color\":\"yellow\",\"text\":\"", "&e")
.replace("[{\"color\":\"white\",\"text\":\"", "&f")
.replace("[{\"obfuscated\":true,\"text\":\"", "&k")
.replace("[{\"bold\":true,\"text\":\"", "&l")
.replace("[{\"strikethrough\":true,\"text\":\"", "&m")
.replace("[{\"underlined\":true,\"text\":\"", "&n")
.replace("[{\"italic\":true,\"text\":\"", "&o").replace("[{\"color\":\"black\",", "&0")
.replace("[{\"color\":\"dark_blue\",", "&1")
.replace("[{\"color\":\"dark_green\",", "&2")
.replace("[{\"color\":\"dark_aqua\",", "&3").replace("[{\"color\":\"dark_red\",", "&4")
.replace("[{\"color\":\"dark_purple\",", "&5").replace("[{\"color\":\"gold\",", "&6")
.replace("[{\"color\":\"gray\",", "&7").replace("[{\"color\":\"dark_gray\",", "&8")
.replace("[{\"color\":\"blue\",", "&9").replace("[{\"color\":\"green\",", "&a")
.replace("[{\"color\":\"aqua\",", "&b").replace("[{\"color\":\"red\",", "&c")
.replace("[{\"color\":\"light_purple\",", "&d").replace("[{\"color\":\"yellow\",", "&e")
.replace("[{\"color\":\"white\",", "&f").replace("[{\"obfuscated\":true,", "&k")
.replace("[{\"bold\":true,", "&l").replace("[{\"strikethrough\":true,", "&m")
.replace("[{\"underlined\":true,", "&n").replace("[{\"italic\":true,", "&o")
.replace("{\"color\":\"black\",\"text\":\"", "&0")
.replace("{\"color\":\"dark_blue\",\"text\":\"", "&1")
.replace("{\"color\":\"dark_green\",\"text\":\"", "&2")
.replace("{\"color\":\"dark_aqua\",\"text\":\"", "&3")
.replace("{\"color\":\"dark_red\",\"text\":\"", "&4")
.replace("{\"color\":\"dark_purple\",\"text\":\"", "&5")
.replace("{\"color\":\"gold\",\"text\":\"", "&6")
.replace("{\"color\":\"gray\",\"text\":\"", "&7")
.replace("{\"color\":\"dark_gray\",\"text\":\"", "&8")
.replace("{\"color\":\"blue\",\"text\":\"", "&9")
.replace("{\"color\":\"green\",\"text\":\"", "&a")
.replace("{\"color\":\"aqua\",\"text\":\"", "&b")
.replace("{\"color\":\"red\",\"text\":\"", "&c")
.replace("{\"color\":\"light_purple\",\"text\":\"", "&d")
.replace("{\"color\":\"yellow\",\"text\":\"", "&e")
.replace("{\"color\":\"white\",\"text\":\"", "&f")
.replace("{\"obfuscated\":true,\"text\":\"", "&k")
.replace("{\"bold\":true,\"text\":\"", "&l")
.replace("{\"strikethrough\":true,\"text\":\"", "&m")
.replace("{\"underlined\":true,\"text\":\"", "&n")
.replace("{\"italic\":true,\"text\":\"", "&o").replace("{\"color\":\"black\",", "&0")
.replace("{\"color\":\"dark_blue\",", "&1").replace("{\"color\":\"dark_green\",", "&2")
.replace("{\"color\":\"dark_aqua\",", "&3").replace("{\"color\":\"dark_red\",", "&4")
.replace("{\"color\":\"dark_purple\",", "&5").replace("{\"color\":\"gold\",", "&6")
.replace("{\"color\":\"gray\",", "&7").replace("{\"color\":\"dark_gray\",", "&8")
.replace("{\"color\":\"blue\",", "&9").replace("{\"color\":\"green\",", "&a")
.replace("{\"color\":\"aqua\",", "&b").replace("{\"color\":\"red\",", "&c")
.replace("{\"color\":\"light_purple\",", "&d").replace("{\"color\":\"yellow\",", "&e")
.replace("{\"color\":\"white\",", "&f").replace("{\"obfuscated\":true,", "&k")
.replace("{\"bold\":true,", "&l").replace("{\"strikethrough\":true,", "&m")
.replace("{\"underlined\":true,", "&n").replace("{\"italic\":true,", "&o")
.replace("\"color\":\"black\",\"text\":\"", "&0")
.replace("\"color\":\"dark_blue\",\"text\":\"", "&1")
.replace("\"color\":\"dark_green\",\"text\":\"", "&2")
.replace("\"color\":\"dark_aqua\",\"text\":\"", "&3")
.replace("\"color\":\"dark_red\",\"text\":\"", "&4")
.replace("\"color\":\"dark_purple\",\"text\":\"", "&5")
.replace("\"color\":\"gold\",\"text\":\"", "&6")
.replace("\"color\":\"gray\",\"text\":\"", "&7")
.replace("\"color\":\"dark_gray\",\"text\":\"", "&8")
.replace("\"color\":\"blue\",\"text\":\"", "&9")
.replace("\"color\":\"green\",\"text\":\"", "&a")
.replace("\"color\":\"aqua\",\"text\":\"", "&b")
.replace("\"color\":\"red\",\"text\":\"", "&c")
.replace("\"color\":\"light_purple\",\"text\":\"", "&d")
.replace("\"color\":\"yellow\",\"text\":\"", "&e")
.replace("\"color\":\"white\",\"text\":\"", "&f")
.replace("\"obfuscated\":true,\"text\":\"", "&k")
.replace("\"bold\":true,\"text\":\"", "&l")
.replace("\"strikethrough\":true,\"text\":\"", "&m")
.replace("\"underlined\":true,\"text\":\"", "&n")
.replace("\"italic\":true,\"text\":\"", "&o").replace("\"color\":\"black\",", "&0")
.replace("\"color\":\"dark_blue\",", "&1").replace("\"color\":\"dark_green\",", "&2")
.replace("\"color\":\"dark_aqua\",", "&3").replace("\"color\":\"dark_red\",", "&4")
.replace("\"color\":\"dark_purple\",", "&5").replace("\"color\":\"gold\",", "&6")
.replace("\"color\":\"gray\",", "&7").replace("\"color\":\"dark_gray\",", "&8")
.replace("\"color\":\"blue\",", "&9").replace("\"color\":\"green\",", "&a")
.replace("\"color\":\"aqua\",", "&b").replace("\"color\":\"red\",", "&c")
.replace("\"color\":\"light_purple\",", "&d").replace("\"color\":\"yellow\",", "&e")
.replace("\"color\":\"white\",", "&f").replace("\"obfuscated\":true,", "&k")
.replace("\"bold\":true,", "&l").replace("\"strikethrough\":true,", "&m")
.replace("\"underlined\":true,", "&n").replace("\"italic\":true,", "&o")
.replace("[{\"text\":\"", "&0").replace("{\"text\":\"", "&0").replace("\"},", "")
.replace("\"}]", "").replace("\"}", "");
str = ChatColor.translateAlternateColorCodes('&', str);
return str;
}
/**
* Restore the TileEntity data to the given world at the given coordinates.
*
* @param worldName World name
* @param x x position
* @param y y position
* @param z z position
* @return true if successful
*/
public boolean restoreTag(String worldName, int x, int y, int z) {
World world = BukkitUtil.getWorld(worldName);
if (world == null) {
return false;
}
return this.restore(world.getBlockAt(x, y, z), data);
return restoreTag(world.getBlockAt(x, y, z));
}
@ApiStatus.Internal
final class Factory {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapper.class.getSimpleName());
private static final String INITIALIZATION_ERROR_TEMPLATE = """
Failed to initialize StateWrapper: {}
Block-/Tile-Entities, pasted by schematics for example, won't be updated with their respective block data. This affects things like sign text, banner patterns, skulls, etc.
Try updating your Server Software, PlotSquared and WorldEdit / FastAsyncWorldEdit first. If the issue persists, report it on the issue tracker.
""";
private static StateWrapper createStateWrapper() {
int[] serverVersion = PlotSquared.platform().serverVersion();
if (PaperLib.isPaper() && (serverVersion[1] == 21 && serverVersion[2] >= 5) || serverVersion[1] > 21) {
try {
return new StateWrapperPaper1_21_5();
} catch (Exception e) {
LOGGER.error("Failed to initialize Paper-specific state wrapper, falling back to Spigot", e);
}
}
try {
return new StateWrapperSpigot();
} catch (Exception e) {
LOGGER.error(INITIALIZATION_ERROR_TEMPLATE, StateWrapperSpigot.class.getSimpleName(), e);
}
return new NoopStateWrapper();
/**
* Restore the TileEntity data to the given block
*
* @param block Block to restore to
* @return true if successful
*/
@SuppressWarnings("deprecation") // #setLine is needed for Spigot compatibility
public boolean restoreTag(@NonNull Block block) {
if (this.tag == null) {
return false;
}
@ApiStatus.Internal
static final class NoopStateWrapper implements StateWrapper {
@Override
public boolean restore(final @NonNull Block block, final @NonNull CompoundTag data) {
org.bukkit.block.BlockState state = block.getState();
switch (getId()) {
case "chest", "beacon", "brewingstand", "dispenser", "dropper", "furnace", "hopper", "shulkerbox" -> {
if (!(state instanceof Container container)) {
return false;
}
List<Tag> itemsTag = this.tag.getListTag("Items").getValue();
Inventory inv = container.getSnapshotInventory();
for (Tag itemTag : itemsTag) {
CompoundTag itemComp = (CompoundTag) itemTag;
ItemType type = ItemType.REGISTRY.get(itemComp.getString("id").toLowerCase());
if (type == null) {
continue;
}
int count = itemComp.getByte("Count");
int slot = itemComp.getByte("Slot");
CompoundTag tag = (CompoundTag) itemComp.getValue().get("tag");
BaseItemStack baseItemStack = new BaseItemStack(type, tag, count);
ItemStack itemStack = BukkitAdapter.adapt(baseItemStack);
inv.setItem(slot, itemStack);
}
container.update(true, false);
return true;
}
case "sign" -> {
if (state instanceof Sign sign) {
sign.setLine(0, jsonToColourCode(tag.getString("Text1")));
sign.setLine(1, jsonToColourCode(tag.getString("Text2")));
sign.setLine(2, jsonToColourCode(tag.getString("Text3")));
sign.setLine(3, jsonToColourCode(tag.getString("Text4")));
state.update(true);
return true;
}
return false;
}
case "skull" -> {
if (state instanceof Skull skull) {
CompoundTag skullOwner = ((CompoundTag) this.tag.getValue().get("SkullOwner"));
if (skullOwner == null) {
return true;
}
String player = skullOwner.getString("Name");
if (player != null && !player.isEmpty()) {
try {
skull.setOwningPlayer(Bukkit.getOfflinePlayer(player));
skull.update(true);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
final CompoundTag properties = (CompoundTag) skullOwner.getValue().get("Properties");
if (properties == null) {
return false;
}
final ListTag textures = properties.getListTag("textures");
if (textures.getValue().isEmpty()) {
return false;
}
final CompoundTag textureCompound = (CompoundTag) textures.getValue().get(0);
if (textureCompound == null) {
return false;
}
String textureValue = textureCompound.getString("Value");
if (textureValue == null) {
return false;
}
if (!PaperLib.isPaper()) {
if (!paperErrorTextureSent) {
paperErrorTextureSent = true;
LOGGER.error("Failed to populate skull data in your road schematic - This is a Spigot limitation.");
}
return false;
}
final PlayerProfile profile = Bukkit.createProfile(UUID.randomUUID());
profile.setProperty(new ProfileProperty("textures", textureValue));
skull.setPlayerProfile(profile);
skull.update(true);
return true;
}
return false;
}
case "banner" -> {
if (state instanceof Banner banner) {
List<Tag> patterns = this.tag.getListTag("Patterns").getValue();
if (patterns == null || patterns.isEmpty()) {
return false;
}
banner.setPatterns(patterns.stream().map(t -> (CompoundTag) t).map(compoundTag -> {
DyeColor color = DyeColor.getByWoolData((byte) compoundTag.getInt("Color"));
PatternType patternType = PatternType.getByIdentifier(compoundTag.getString("Pattern"));
if (color == null || patternType == null) {
return null;
}
return new Pattern(color, patternType);
}).filter(Objects::nonNull).toList());
banner.update(true);
return true;
}
return false;
}
}
return false;
}
public String getId() {
String tileid = this.tag.getString("id").toLowerCase();
if (tileid.startsWith("minecraft:")) {
tileid = tileid.replace("minecraft:", "");
}
return tileid;
}
public List<CompoundTag> serializeInventory(ItemStack[] items) {
List<CompoundTag> tags = new ArrayList<>();
for (int i = 0; i < items.length; ++i) {
if (items[i] != null) {
Map<String, Tag> tagData = serializeItem(items[i]);
tagData.put("Slot", new ByteTag((byte) i));
tags.add(new CompoundTag(tagData));
}
}
return tags;
}
public Map<String, Tag> serializeItem(ItemStack item) {
Map<String, Tag> data = new HashMap<>();
data.put("id", new StringTag(item.getType().name()));
data.put("Damage", new ShortTag(item.getDurability()));
data.put("Count", new ByteTag((byte) item.getAmount()));
if (!item.getEnchantments().isEmpty()) {
List<CompoundTag> enchantmentList = new ArrayList<>();
for (Entry<Enchantment, Integer> entry : item.getEnchantments().entrySet()) {
Map<String, Tag> enchantment = new HashMap<>();
enchantment.put("id", new StringTag(entry.getKey().toString()));
enchantment.put("lvl", new ShortTag(entry.getValue().shortValue()));
enchantmentList.add(new CompoundTag(enchantment));
}
Map<String, Tag> auxData = new HashMap<>();
auxData.put("ench", new ListTag(CompoundTag.class, enchantmentList));
data.put("tag", new CompoundTag(auxData));
}
return data;
}
}

View File

@@ -1,155 +0,0 @@
/*
* PlotSquared, a land and world management plugin for Minecraft.
* Copyright (C) IntellectualSites <https://intellectualsites.com>
* Copyright (C) IntellectualSites team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.bukkit.schematic;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.Tag;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.DyeColor;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.bukkit.block.sign.SignSide;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.invoke.MethodHandle;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
final class StateWrapperPaper1_21_5 extends StateWrapperSpigot {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapperPaper1_21_5.class.getSimpleName());
private static final Gson GSON = new GsonBuilder().registerTypeHierarchyAdapter(Tag.class, new NbtGsonSerializer()).create();
private static Object KYORI_GSON_SERIALIZER = null;
private static MethodHandle GSON_SERIALIZER_DESERIALIZE_TREE = null;
private static MethodHandle BUKKIT_SIGN_SIDE_LINE_SET = null;
public StateWrapperPaper1_21_5() {
super();
try {
initializeSignHack();
LOGGER.info("Using {} for block data population", StateWrapperPaper1_21_5.class.getSimpleName());
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize sign hack", e);
}
}
@Override
public void postEntityStateLoad(final @NonNull BlockState blockState, final @NonNull CompoundTag data) throws Throwable {
// signs need special handling during generation
if (blockState instanceof Sign sign) {
if (data.getValue().get("front_text") instanceof CompoundTag textTag) {
setSignTextHack(sign.getSide(Side.FRONT), textTag);
}
if (data.getValue().get("back_text") instanceof CompoundTag textTag) {
setSignTextHack(sign.getSide(Side.BACK), textTag);
}
}
}
@Override
public Logger logger() {
return StateWrapperPaper1_21_5.LOGGER;
}
/**
* Set sign content on the bukkit tile entity. The server does not load sign content applied via the main logic
* (CraftBlockEntity#load), as the SignEntity needs to have a valid ServerLevel assigned to it.
* That's not possible on worldgen; therefore, this hack has to be used additionally.
* <br />
* Modern sign content (non-plain-text sign lines) require Paper.
*
* @param side The sign side to apply data onto.
* @param text The compound tag containing the data for the sign side ({@code front_text} / {@code back_text})
* @throws Throwable if something went wrong when reflectively updating the sign.
*/
private static void setSignTextHack(SignSide side, CompoundTag text) throws Throwable {
if (text.containsKey("color")) {
//noinspection UnstableApiUsage
side.setColor(DyeColor.legacyValueOf(text.getString("color").toUpperCase(Locale.ROOT)));
}
if (text.containsKey("has_glowing_text")) {
side.setGlowingText(text.getByte("has_glowing_text") == 1);
}
List<Tag> lines = text.getList("messages");
if (lines != null) {
for (int i = 0; i < Math.min(lines.size(), 3); i++) {
Tag line = lines.get(i);
Object content = line.getValue();
// Minecraft uses mixed lists / arrays in their sign texts. One line can be a complex component, whereas
// the following line could simply be a string. Those simpler lines are represented as `{"": ""}` (only in
// SNBT those will be shown as a standard string).
if (line instanceof CompoundTag compoundTag && compoundTag.getValue().containsKey("")) {
content = compoundTag.getValue().get("");
}
// absolute garbage way to try to handle stringified components (pre 1.21.5)
else if (content instanceof String contentAsString && (contentAsString.startsWith("{") || contentAsString.startsWith("["))) {
try {
content = JsonParser.parseString(contentAsString);
} catch (JsonSyntaxException e) {
// well, it wasn't JSON after all
}
}
// serializes the line content from JNBT to Gson JSON objects, passes that to adventure and deserializes
// into an adventure component.
// pass all possible types of content into the deserializer (Strings, Compounds, Arrays), even though Strings
// could be set directly via Sign#setLine(int, String). The overhead is minimal, the serializer can handle
// strings - and we don't have to use the deprecated method.
BUKKIT_SIGN_SIDE_LINE_SET.invoke(
side, i, GSON_SERIALIZER_DESERIALIZE_TREE.invoke(
KYORI_GSON_SERIALIZER,
content instanceof JsonElement ? content : GSON.toJsonTree(content)
)
);
}
}
}
private static void initializeSignHack() throws Throwable {
char[] dontObfuscate = new char[]{
'n', 'e', 't', '.', 'k', 'y', 'o', 'r', 'i', '.', 'a', 'd', 'v', 'e', 'n', 't', 'u', 'r', 'e', '.',
't', 'e', 'x', 't', '.', 's', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e', 'r', '.', 'g', 's', 'o', 'n', '.',
'G', 's', 'o', 'n', 'C', 'o', 'm', 'p', 'o', 'n', 'e', 'n', 't', 'S', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e', 'r'
};
Class<?> gsonComponentSerializerClass = Class.forName(new String(dontObfuscate));
KYORI_GSON_SERIALIZER = Arrays.stream(gsonComponentSerializerClass.getMethods())
.filter(method -> method.getName().equals("gson"))
.findFirst()
.orElseThrow().invoke(null);
GSON_SERIALIZER_DESERIALIZE_TREE = LOOKUP.unreflect(Arrays
.stream(gsonComponentSerializerClass.getMethods())
.filter(method -> method.getName().equals("deserializeFromTree") && method.getParameterCount() == 1)
.findFirst()
.orElseThrow());
BUKKIT_SIGN_SIDE_LINE_SET = LOOKUP.unreflect(Arrays.stream(SignSide.class.getMethods())
.filter(method -> method.getName().equals("line") && method.getParameterCount() == 2)
.findFirst()
.orElseThrow());
}
}

View File

@@ -1,210 +0,0 @@
/*
* PlotSquared, a land and world management plugin for Minecraft.
* Copyright (C) IntellectualSites <https://intellectualsites.com>
* Copyright (C) IntellectualSites team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.bukkit.schematic;
import com.plotsquared.core.util.ReflectionUtils;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.extension.platform.NoCapablePlatformException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.function.Consumer;
sealed class StateWrapperSpigot implements StateWrapper permits StateWrapperPaper1_21_5 {
private static final boolean FORCE_UPDATE_STATE = true;
private static final boolean UPDATE_TRIGGER_PHYSICS = false;
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackageName();
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapperSpigot.class.getSimpleName());
static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static BukkitImplAdapter ADAPTER = null;
private static Class<?> LIN_TAG_CLASS = null;
private static Class<?> JNBT_TAG_CLASS = null;
private static Class<?> CRAFT_BLOCK_ENTITY_STATE_CLASS = null;
private static MethodHandle PAPERWEIGHT_ADAPTER_FROM_NATIVE = null;
private static MethodHandle CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA = null;
private static MethodHandle CRAFT_BLOCK_ENTITY_STATE_UPDATE = null;
private static MethodHandle TO_LIN_TAG = null;
public StateWrapperSpigot() {
try {
findNbtCompoundClassType(clazz -> LIN_TAG_CLASS = clazz, clazz -> JNBT_TAG_CLASS = clazz);
ReflectionUtils.RefClass worldEditPluginRefClass = ReflectionUtils.getRefClass(WorldEditPlugin.class);
WorldEditPlugin worldEditPlugin = (WorldEditPlugin) worldEditPluginRefClass
.getMethod("getInstance")
.of(null)
.call();
ADAPTER = (BukkitImplAdapter) worldEditPluginRefClass
.getMethod("getBukkitImplAdapter")
.of(worldEditPlugin)
.call();
PAPERWEIGHT_ADAPTER_FROM_NATIVE = findPaperweightAdapterFromNativeMethodHandle(
ADAPTER.getClass(), LIN_TAG_CLASS, JNBT_TAG_CLASS
);
TO_LIN_TAG = findToLinTagMethodHandle(LIN_TAG_CLASS);
} catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | NoCapablePlatformException e) {
throw new RuntimeException("Failed to access required WorldEdit methods", e);
}
try {
CRAFT_BLOCK_ENTITY_STATE_CLASS = Class.forName(CRAFTBUKKIT_PACKAGE + ".block.CraftBlockEntityState");
CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA = findCraftBlockEntityStateLoadDataMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
CRAFT_BLOCK_ENTITY_STATE_UPDATE = findCraftBlockEntityStateUpdateMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException("Failed to initialize required native method accessors", e);
}
}
@Override
public boolean restore(final @NonNull Block block, final @NonNull CompoundTag data) {
try {
final BlockState blockState = block.getState();
if (!CRAFT_BLOCK_ENTITY_STATE_CLASS.isAssignableFrom(blockState.getClass())) {
return false;
}
// get native tag
Object nativeTag = PAPERWEIGHT_ADAPTER_FROM_NATIVE.invoke(
ADAPTER,
LIN_TAG_CLASS == null ? data : TO_LIN_TAG.invoke(data)
);
// load block entity data
CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA.invoke(blockState, nativeTag);
postEntityStateLoad(blockState, data);
CRAFT_BLOCK_ENTITY_STATE_UPDATE.invoke(blockState, FORCE_UPDATE_STATE, UPDATE_TRIGGER_PHYSICS);
} catch (Throwable e) {
logger().error("Failed to update tile entity", e);
}
return false;
}
public void postEntityStateLoad(final @NonNull BlockState blockState, final @NonNull CompoundTag data) throws Throwable {
}
public Logger logger() {
return StateWrapperSpigot.LOGGER;
}
/**
* Initialize the used NBT tag class. For modern FAWE and WE that'll be Lin - for older ones JNBT.
*
* @throws ClassNotFoundException if neither can be found.
*/
private static void findNbtCompoundClassType(Consumer<Class<?>> linClass, Consumer<Class<?>> jnbtClass) throws
ClassNotFoundException {
try {
linClass.accept(Class.forName("org.enginehub.linbus.tree.LinTag"));
} catch (ClassNotFoundException e) {
jnbtClass.accept(Class.forName("com.sk89q.jnbt.Tag"));
}
}
/**
* Finds the {@code toLinTag} method on the {@code ToLinTag} interface, if lin-bus is available in the classpath.
* <br />
* Required to access the underlying lin tag of the used JNBT tag by PlotSquared, so it can be converted into the platforms
* native tag later.
*
* @param linTagClass {@code Tag} class of lin-bus, or {@code null} if not available.
* @return the MethodHandle for {@code toLinTag}, or {@code null} if lin-bus is not available in the classpath.
* @throws ClassNotFoundException if the {@code ToLinTag} class could not be found.
* @throws NoSuchMethodException if no {@code toLinTag} method exists.
* @throws IllegalAccessException shouldn't happen.
*/
private static MethodHandle findToLinTagMethodHandle(Class<?> linTagClass) throws ClassNotFoundException,
NoSuchMethodException, IllegalAccessException {
if (linTagClass == null) {
return null;
}
return LOOKUP.findVirtual(
Class.forName("org.enginehub.linbus.tree.ToLinTag"),
"toLinTag",
MethodType.methodType(linTagClass)
);
}
/**
* Find the method (handle) to convert from native (= WE/FAWE) NBT tags to minecraft NBT tags.
* <br />
* Depending on the used version of WE/FAWE, this differs:
* <ul>
* <li>On WE/FAWE version pre LinBus introduction: {@code fromNative(org.sk89q.jnbt.Tag)}</li>
* <li>On WE versions post LinBus introduction: {@code fromNative(org.enginehub.linbus.tree.LinTag)}</li>
* <li>On FAWE versions post LinBus introduction: {@code fromNativeLin(org.enginehub.linbus.tree.LinTag)}</li>
* </ul>
*
* @param adapterClass The bukkit adapter implementation class
* @param linTagClass The lin-bus {@code Tag} class, if existing - otherwise {@code null}
* @param jnbtTagClass The jnbt {@code Tag} class, if lin-bus was not found in classpath - otherwise {@code null}
* @return the method.
* @throws IllegalAccessException shouldn't happen as private lookup is used.
* @throws NoSuchMethodException if the method couldn't be found.
*/
private static MethodHandle findPaperweightAdapterFromNativeMethodHandle(
Class<?> adapterClass, Class<?> linTagClass, Class<?> jnbtTagClass
) throws IllegalAccessException, NoSuchMethodException {
final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(adapterClass, LOOKUP);
if (jnbtTagClass != null) {
// usage of JNBT = identical method signatures for WE and FAWE
return lookup.findVirtual(adapterClass, "fromNative", MethodType.methodType(Object.class, jnbtTagClass));
}
try {
// FAWE
return lookup.findVirtual(adapterClass, "fromNativeLin", MethodType.methodType(Object.class, linTagClass));
} catch (NoSuchMethodException e) {
// WE
return lookup.findVirtual(adapterClass, "fromNative", MethodType.methodType(Object.class, linTagClass));
}
}
private static MethodHandle findCraftBlockEntityStateLoadDataMethodHandle(Class<?> craftBlockEntityStateClass) throws
NoSuchMethodException, IllegalAccessException, ClassNotFoundException {
for (final Method method : craftBlockEntityStateClass.getMethods()) {
if (method.getName().equals("loadData") && method.getParameterCount() == 1) {
return LOOKUP.unreflect(method);
}
}
throw new NoSuchMethodException("Couldn't find #loadData(CompoundTag) in " + craftBlockEntityStateClass.getName());
}
private static MethodHandle findCraftBlockEntityStateUpdateMethodHandle(Class<?> craftBlockEntityStateClass) throws
NoSuchMethodException, IllegalAccessException, ClassNotFoundException {
for (final Method method : craftBlockEntityStateClass.getMethods()) {
if (method.getReturnType().equals(Boolean.TYPE) && method.getParameterCount() == 2 &&
method.getParameterTypes()[0] == Boolean.TYPE && method.getParameterTypes()[1] == Boolean.TYPE) {
return LOOKUP.unreflect(method);
}
}
throw new NoSuchMethodException("Couldn't find method for #update(boolean, boolean) in " + craftBlockEntityStateClass.getName());
}
}

View File

@@ -2,7 +2,7 @@
# Platform expectations
paper = "1.20.4-R0.1-SNAPSHOT"
guice = "7.0.0"
spotbugs = "4.9.8"
spotbugs = "4.9.6"
checkerqual = "3.51.1"
gson = "2.10"
guava = "31.1-jre"
@@ -13,7 +13,7 @@ log4j = "2.19.0"
# Plugins
worldedit = "7.2.20"
fawe = "2.14.0"
fawe = "2.13.2"
placeholderapi = "2.11.6"
luckperms = "5.5"
essentialsx = "2.21.2"