9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-25 18:09:27 +00:00

fix(bukkit): 修复1.21.6+物品展示框及客户端侧方块标签失效问题

This commit is contained in:
jhqwqmc
2025-07-13 11:58:59 +08:00
parent 022e401b89
commit 497372ae35
12 changed files with 175 additions and 113 deletions

View File

@@ -36,7 +36,6 @@ import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.world.GenericGameEvent;
import org.bukkit.inventory.ItemStack;
@@ -53,14 +52,6 @@ public final class BlockEventListener implements Listener {
this.enableNoteBlockCheck = enableNoteBlockCheck;
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Object packet = this.manager.cachedUpdateTagsPacket;
if (packet != null) {
this.plugin.adapt(event.getPlayer()).sendPacket(packet, false);
}
}
@EventHandler(ignoreCancelled = true)
public void onPlayerAttack(EntityDamageByEntityEvent event) {
if (!VersionHelper.isOrAbove1_20_5()) {

View File

@@ -94,7 +94,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
private BlockEventListener blockEventListener;
private FallingBlockRemoveListener fallingBlockRemoveListener;
// cached tag packet
Object cachedUpdateTagsPacket;
private Object cachedUpdateTagsPacket;
private final List<Tuple<Object, Key, Boolean>> blocksToDeceive = new ArrayList<>();
@@ -397,6 +397,10 @@ public final class BukkitBlockManager extends AbstractBlockManager {
}
}
public Object cachedUpdateTagsPacket() {
return cachedUpdateTagsPacket;
}
public class BlockParser implements ConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"};

View File

@@ -158,6 +158,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
registerNMSPacketConsumer(PacketConsumers.SET_ENTITY_MOTION, NetworkReflections.clazz$ClientboundSetEntityMotionPacket);
registerNMSPacketConsumer(PacketConsumers.FINISH_CONFIGURATION, NetworkReflections.clazz$ClientboundFinishConfigurationPacket);
registerNMSPacketConsumer(PacketConsumers.LOGIN_FINISHED, NetworkReflections.clazz$ClientboundLoginFinishedPacket);
registerNMSPacketConsumer(PacketConsumers.UPDATE_TAGS, NetworkReflections.clazz$ClientboundUpdateTagsPacket);
registerS2CByteBufPacketConsumer(PacketConsumers.LEVEL_CHUNK_WITH_LIGHT, this.packetIds.clientboundLevelChunkWithLightPacket());
registerS2CByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket());
registerS2CByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket());

View File

@@ -133,8 +133,8 @@ public class PacketConsumers {
ADD_ENTITY_HANDLERS[MEntityTypes.TEXT_DISPLAY$registryId] = simpleAddEntityHandler(TextDisplayPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[MEntityTypes.ARMOR_STAND$registryId] = simpleAddEntityHandler(ArmorStandPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[MEntityTypes.ITEM$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[MEntityTypes.ITEM_FRAME$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[MEntityTypes.GLOW_ITEM_FRAME$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[MEntityTypes.ITEM_FRAME$registryId] = simpleAddEntityHandler(ItemFramePacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[MEntityTypes.GLOW_ITEM_FRAME$registryId] = simpleAddEntityHandler(ItemFramePacketHandler.INSTANCE);
ADD_ENTITY_HANDLERS[MEntityTypes.FIREBALL$registryId] = createOptionalCustomProjectileEntityHandler(true);
ADD_ENTITY_HANDLERS[MEntityTypes.EYE_OF_ENDER$registryId] = createOptionalCustomProjectileEntityHandler(true);
ADD_ENTITY_HANDLERS[MEntityTypes.FIREWORK_ROCKET$registryId] = createOptionalCustomProjectileEntityHandler(true);
@@ -1935,24 +1935,21 @@ public class PacketConsumers {
for (int i = 0; i < packedItems.size(); i++) {
Object packedItem = packedItems.get(i);
int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem);
if (entityDataId == EntityDataUtils.CUSTOM_NAME_DATA_ID) {
Optional<Object> optionalTextComponent = (Optional<Object>) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (optionalTextComponent.isPresent()) {
Object textComponent = optionalTextComponent.get();
String json = ComponentUtils.minecraftToJson(textComponent);
Map<String, Component> tokens = CraftEngine.instance().fontManager().matchTags(json);
if (!tokens.isEmpty()) {
Component component = AdventureHelper.jsonToComponent(json);
for (Map.Entry<String, Component> token : tokens.entrySet()) {
component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue()));
}
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component))));
isChanged = true;
break;
}
}
if (entityDataId != EntityDataUtils.CUSTOM_NAME_DATA_ID) continue;
Optional<Object> optionalTextComponent = (Optional<Object>) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (optionalTextComponent.isEmpty()) continue;
Object textComponent = optionalTextComponent.get();
String json = ComponentUtils.minecraftToJson(textComponent);
Map<String, Component> tokens = CraftEngine.instance().fontManager().matchTags(json);
if (tokens.isEmpty()) continue;
Component component = AdventureHelper.jsonToComponent(json);
for (Map.Entry<String, Component> token : tokens.entrySet()) {
component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue()));
}
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component))));
isChanged = true;
break;
}
if (isChanged) {
event.setChanged(true);
@@ -2573,4 +2570,14 @@ public class PacketConsumers {
CraftEngine.instance().logger().warn("Failed to handle ClientboundUpdateAdvancementsPacket", e);
}
};
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> UPDATE_TAGS = (user, event, packet) -> {
try {
Object modifiedPacket = BukkitBlockManager.instance().cachedUpdateTagsPacket();
if (packet.equals(modifiedPacket) || modifiedPacket == null) return;
event.replacePacket(modifiedPacket);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ClientboundUpdateTagsPacket", e);
}
};
}

View File

@@ -32,25 +32,22 @@ public class ArmorStandPacketHandler implements EntityPacketHandler {
for (int i = 0; i < packedItems.size(); i++) {
Object packedItem = packedItems.get(i);
int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem);
if (entityDataId == EntityDataUtils.CUSTOM_NAME_DATA_ID) {
@SuppressWarnings("unchecked")
Optional<Object> optionalTextComponent = (Optional<Object>) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (optionalTextComponent.isPresent()) {
Object textComponent = optionalTextComponent.get();
String json = ComponentUtils.minecraftToJson(textComponent);
Map<String, Component> tokens = CraftEngine.instance().fontManager().matchTags(json);
if (!tokens.isEmpty()) {
Component component = AdventureHelper.jsonToComponent(json);
for (Map.Entry<String, Component> token : tokens.entrySet()) {
component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue()));
}
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component))));
isChanged = true;
break;
}
}
if (entityDataId != EntityDataUtils.CUSTOM_NAME_DATA_ID) continue;
@SuppressWarnings("unchecked")
Optional<Object> optionalTextComponent = (Optional<Object>) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (optionalTextComponent.isEmpty()) continue;
Object textComponent = optionalTextComponent.get();
String json = ComponentUtils.minecraftToJson(textComponent);
Map<String, Component> tokens = CraftEngine.instance().fontManager().matchTags(json);
if (tokens.isEmpty()) continue;
Component component = AdventureHelper.jsonToComponent(json);
for (Map.Entry<String, Component> token : tokens.entrySet()) {
component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue()));
}
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component))));
isChanged = true;
break;
}
if (isChanged) {
event.setChanged(true);

View File

@@ -47,22 +47,20 @@ public class BlockDisplayPacketHandler implements EntityPacketHandler {
} else if (Config.interceptEntityName() && entityDataId == EntityDataUtils.CUSTOM_NAME_DATA_ID) {
@SuppressWarnings("unchecked")
Optional<Object> optionalTextComponent = (Optional<Object>) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (optionalTextComponent.isPresent()) {
Object textComponent = optionalTextComponent.get();
String json = ComponentUtils.minecraftToJson(textComponent);
Map<String, Component> tokens = CraftEngine.instance().fontManager().matchTags(json);
if (!tokens.isEmpty()) {
Component component = AdventureHelper.jsonToComponent(json);
for (Map.Entry<String, Component> token : tokens.entrySet()) {
component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue()));
}
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(
entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component))
));
isChanged = true;
}
if (optionalTextComponent.isEmpty()) continue;
Object textComponent = optionalTextComponent.get();
String json = ComponentUtils.minecraftToJson(textComponent);
Map<String, Component> tokens = CraftEngine.instance().fontManager().matchTags(json);
if (tokens.isEmpty()) continue;
Component component = AdventureHelper.jsonToComponent(json);
for (Map.Entry<String, Component> token : tokens.entrySet()) {
component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue()));
}
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(
entityDataId, serializer, Optional.of(ComponentUtils.adventureToMinecraft(component))
));
isChanged = true;
}
}
if (isChanged) {

View File

@@ -28,30 +28,28 @@ public class CommonItemPacketHandler implements EntityPacketHandler {
for (int i = 0; i < packedItems.size(); i++) {
Object packedItem = packedItems.get(i);
int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem);
if (entityDataId == EntityDataUtils.ITEM_DATA_ID) {
Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (!CoreReflections.clazz$ItemStack.isInstance(nmsItemStack)) {
long time = System.currentTimeMillis();
if (time - lastWarningTime > 5000) {
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
CraftEngine.instance().logger().severe("An issue was detected while applying item-related entity data for '" + serverPlayer.name() +
"'. Please execute the command '/ce debug entity-id " + serverPlayer.world().name() + " " + id + "' and provide a screenshot for further investigation.");
lastWarningTime = time;
}
continue;
}
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(itemStack, (BukkitServerPlayer) user);
if (optional.isPresent()) {
isChanged = true;
itemStack = optional.get();
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(
entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)
));
break;
if (entityDataId != EntityDataUtils.ITEM_DATA_ID) continue;
Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (!CoreReflections.clazz$ItemStack.isInstance(nmsItemStack)) {
long time = System.currentTimeMillis();
if (time - lastWarningTime > 5000) {
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
CraftEngine.instance().logger().severe("An issue was detected while applying item-related entity data for '" + serverPlayer.name() +
"'. Please execute the command '/ce debug entity-id " + serverPlayer.world().name() + " " + id + "' and provide a screenshot for further investigation.");
lastWarningTime = time;
}
continue;
}
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(itemStack, (BukkitServerPlayer) user);
if (optional.isEmpty()) continue;
isChanged = true;
itemStack = optional.get();
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(
entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)
));
break;
}
if (isChanged) {
event.setChanged(true);

View File

@@ -25,20 +25,18 @@ public class ItemDisplayPacketHandler implements EntityPacketHandler {
for (int i = 0; i < packedItems.size(); i++) {
Object packedItem = packedItems.get(i);
int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem);
if (entityDataId == EntityDataUtils.DISPLAYED_ITEM_DATA_ID) {
Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(itemStack, (BukkitServerPlayer) user);
if (optional.isPresent()) {
isChanged = true;
itemStack = optional.get();
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(
entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)
));
break;
}
}
if (entityDataId != EntityDataUtils.DISPLAYED_ITEM_DATA_ID) continue;
Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(itemStack, (BukkitServerPlayer) user);
if (optional.isEmpty()) continue;
isChanged = true;
itemStack = optional.get();
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(
entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)
));
break;
}
if (isChanged) {
event.setChanged(true);

View File

@@ -0,0 +1,62 @@
package net.momirealms.craftengine.bukkit.plugin.network.handler;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.EntityDataUtils;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent;
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.Optional;
public class ItemFramePacketHandler implements EntityPacketHandler {
public static final ItemFramePacketHandler INSTANCE = new ItemFramePacketHandler();
private static long lastWarningTime = 0;
@Override
public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) {
FriendlyByteBuf buf = event.getBuffer();
int id = buf.readVarInt();
boolean isChanged = false;
List<Object> packedItems = FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$unpack(buf);
for (int i = 0; i < packedItems.size(); i++) {
Object packedItem = packedItems.get(i);
int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem);
if (entityDataId != EntityDataUtils.ITEM_FRAME_DATA_ID) continue;
Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (!CoreReflections.clazz$ItemStack.isInstance(nmsItemStack)) {
long time = System.currentTimeMillis();
if (time - lastWarningTime > 5000) {
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
CraftEngine.instance().logger().severe("An issue was detected while applying item-related entity data for '" + serverPlayer.name() +
"'. Please execute the command '/ce debug entity-id " + serverPlayer.world().name() + " " + id + "' and provide a screenshot for further investigation.");
lastWarningTime = time;
}
continue;
}
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(itemStack, (BukkitServerPlayer) user);
if (optional.isEmpty()) continue;
isChanged = true;
itemStack = optional.get();
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(
entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)
));
break;
}
if (isChanged) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeVarInt(id);
FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$pack(packedItems, buf);
}
}
}

View File

@@ -31,22 +31,20 @@ public class TextDisplayPacketHandler implements EntityPacketHandler {
for (int i = 0; i < packedItems.size(); i++) {
Object packedItem = packedItems.get(i);
int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem);
if (entityDataId == EntityDataUtils.TEXT_DATA_ID) {
Object textComponent = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (textComponent == CoreReflections.instance$Component$empty) break;
String json = ComponentUtils.minecraftToJson(textComponent);
Map<String, Component> tokens = CraftEngine.instance().fontManager().matchTags(json);
if (!tokens.isEmpty()) {
Component component = AdventureHelper.jsonToComponent(json);
for (Map.Entry<String, Component> token : tokens.entrySet()) {
component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue()));
}
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, ComponentUtils.adventureToMinecraft(component)));
isChanged = true;
break;
}
if (entityDataId != EntityDataUtils.TEXT_DATA_ID) continue;
Object textComponent = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem);
if (textComponent == CoreReflections.instance$Component$empty) break;
String json = ComponentUtils.minecraftToJson(textComponent);
Map<String, Component> tokens = CraftEngine.instance().fontManager().matchTags(json);
if (tokens.isEmpty()) continue;
Component component = AdventureHelper.jsonToComponent(json);
for (Map.Entry<String, Component> token : tokens.entrySet()) {
component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue()));
}
Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem);
packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue(entityDataId, serializer, ComponentUtils.adventureToMinecraft(component)));
isChanged = true;
break;
}
if (isChanged) {
event.setChanged(true);

View File

@@ -1587,4 +1587,11 @@ public final class NetworkReflections {
"network.protocol.game.ClientboundUpdateAdvancementsPacket"
)
);
public static final Class<?> clazz$ClientboundUpdateTagsPacket = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
List.of("network.protocol.common.ClientboundUpdateTagsPacket", "network.protocol.game.PacketPlayOutTags"),
List.of("network.protocol.common.ClientboundUpdateTagsPacket", "network.protocol.game.ClientboundUpdateTagsPacket")
)
);
}

View File

@@ -16,6 +16,7 @@ public class EntityDataUtils {
public static final int DISPLAYED_ITEM_DATA_ID = VersionHelper.isOrAbove1_20_2() ? 23 : 22;
public static final int CUSTOM_NAME_DATA_ID = 2;
public static final int ITEM_DATA_ID = 8;
public static final int ITEM_FRAME_DATA_ID = VersionHelper.isOrAbove1_21_6() ? 9 : 8;
public static byte encodeTextDisplayMask(boolean hasShadow, boolean isSeeThrough, boolean useDefaultBackground, int alignment) {
int bitMask = 0;