mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-31 04:46:37 +00:00
新增客户端侧block tag
This commit is contained in:
@@ -33,6 +33,7 @@ import org.bukkit.event.block.BlockPhysicsEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.world.GenericGameEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
@@ -49,6 +50,14 @@ public class BlockEventListener implements Listener {
|
||||
this.enableNoteBlockCheck = enableNoteBlockCheck;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Object packet = this.manager.cachedUpdateTagsPacket;
|
||||
if (packet != null) {
|
||||
this.plugin.networkManager().sendPacket(event.getPlayer(), packet);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerAttack(EntityDamageByEntityEvent event) {
|
||||
if (!VersionHelper.isOrAbove1_20_5()) {
|
||||
|
||||
@@ -11,10 +11,7 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.KeyUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.Reflections;
|
||||
import net.momirealms.craftengine.bukkit.util.RegistryUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.*;
|
||||
import net.momirealms.craftengine.core.block.*;
|
||||
import net.momirealms.craftengine.core.block.properties.Properties;
|
||||
import net.momirealms.craftengine.core.block.properties.Property;
|
||||
@@ -34,12 +31,15 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigExce
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.registry.Registries;
|
||||
import net.momirealms.craftengine.core.registry.WritableRegistry;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -49,6 +49,7 @@ import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BukkitBlockManager extends AbstractBlockManager {
|
||||
private static BukkitBlockManager instance;
|
||||
@@ -92,6 +93,10 @@ public class BukkitBlockManager extends AbstractBlockManager {
|
||||
private final BlockEventListener blockEventListener;
|
||||
private final FallingBlockRemoveListener fallingBlockRemoveListener;
|
||||
|
||||
private Map<Integer, List<String>> clientBoundTags = Map.of();
|
||||
private Map<Integer, List<String>> previousTags = Map.of();
|
||||
protected Object cachedUpdateTagsPacket;
|
||||
|
||||
public BukkitBlockManager(BukkitCraftEngine plugin) {
|
||||
super(plugin);
|
||||
this.plugin = plugin;
|
||||
@@ -146,6 +151,8 @@ public class BukkitBlockManager extends AbstractBlockManager {
|
||||
this.modBlockStates.clear();
|
||||
if (EmptyBlock.STATE != null)
|
||||
Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.STATE);
|
||||
this.previousTags = this.clientBoundTags;
|
||||
this.clientBoundTags = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,6 +172,26 @@ public class BukkitBlockManager extends AbstractBlockManager {
|
||||
initSuggestions();
|
||||
resetPacketConsumers();
|
||||
clearCache();
|
||||
resendTags();
|
||||
}
|
||||
|
||||
private void resendTags() {
|
||||
// if there's no change
|
||||
if (this.clientBoundTags.equals(this.previousTags)) return;
|
||||
List<TagUtils.TagEntry> list = new ArrayList<>();
|
||||
for (Map.Entry<Integer, List<String>> entry : this.clientBoundTags.entrySet()) {
|
||||
list.add(new TagUtils.TagEntry(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
Object packet = TagUtils.createUpdateTagsPacket(Map.of(Reflections.instance$Registries$BLOCK, list));
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
this.plugin.networkManager().sendPacket(player, packet);
|
||||
}
|
||||
// 如果空,那么新来的玩家就没必要收到更新包了
|
||||
if (list.isEmpty()) {
|
||||
this.cachedUpdateTagsPacket = null;
|
||||
} else {
|
||||
this.cachedUpdateTagsPacket = packet;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearCache() {
|
||||
@@ -333,10 +360,18 @@ public class BukkitBlockManager extends AbstractBlockManager {
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
// check duplicated config
|
||||
if (byId.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.block.duplicate");
|
||||
if (id.namespace().equals("minecraft") && Registry.MATERIAL.get(KeyUtils.toNamespacedKey(id)) != null) {
|
||||
parseVanillaBlock(pack, path, id, section);
|
||||
} else {
|
||||
// check duplicated config
|
||||
if (byId.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.block.duplicate");
|
||||
}
|
||||
parseCustomBlock(pack, path, id, section);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseCustomBlock(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
// read block settings
|
||||
BlockSettings settings = BlockSettings.fromMap(MiscUtils.castToMap(section.getOrDefault("settings", Map.of()), false));
|
||||
|
||||
@@ -458,14 +493,14 @@ public class BukkitBlockManager extends AbstractBlockManager {
|
||||
|
||||
Map<String, Object> behaviors = MiscUtils.castToMap(section.getOrDefault("behavior", Map.of()), false);
|
||||
CustomBlock block = BukkitCustomBlock.builder(id)
|
||||
.appearances(appearances)
|
||||
.variantMapper(variants)
|
||||
.lootTable(lootTable)
|
||||
.properties(properties)
|
||||
.settings(settings)
|
||||
.behavior(behaviors)
|
||||
.events(events)
|
||||
.build();
|
||||
.appearances(appearances)
|
||||
.variantMapper(variants)
|
||||
.lootTable(lootTable)
|
||||
.properties(properties)
|
||||
.settings(settings)
|
||||
.behavior(behaviors)
|
||||
.events(events)
|
||||
.build();
|
||||
|
||||
// bind appearance and real state
|
||||
for (ImmutableBlockState state : block.variantProvider().states()) {
|
||||
@@ -479,7 +514,7 @@ public class BukkitBlockManager extends AbstractBlockManager {
|
||||
appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new ArrayList<>()).add(state.customBlockState().registryId());
|
||||
}
|
||||
|
||||
byId.put(id, block);
|
||||
BukkitBlockManager.this.byId.put(id, block);
|
||||
|
||||
// generate mod assets
|
||||
if (Config.generateModAssets()) {
|
||||
@@ -489,6 +524,23 @@ public class BukkitBlockManager extends AbstractBlockManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseVanillaBlock(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
Map<String, Object> settings = MiscUtils.castToMap(section.get("settings"), true);
|
||||
if (settings != null) {
|
||||
Object clientBoundTags = settings.get("client-bound-tags");
|
||||
if (clientBoundTags instanceof List<?> list) {
|
||||
List<String> clientSideTags = MiscUtils.getAsStringList(list).stream().filter(ResourceLocation::isValid).toList();
|
||||
try {
|
||||
Object nmsBlock = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$BLOCK, KeyUtils.toResourceLocation(id));
|
||||
FastNMS.INSTANCE.method$IdMap$getId(Reflections.instance$BuiltInRegistries$BLOCK, nmsBlock).ifPresent(i ->
|
||||
BukkitBlockManager.this.clientBoundTags.put(i, clientSideTags));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
BukkitBlockManager.this.plugin.logger().warn("Unable to get block " + id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Property<?>> parseProperties(Map<String, Object> propertiesSection) {
|
||||
|
||||
@@ -51,7 +51,6 @@ public class TestCommand extends BukkitCommandFeature<CommandSender> {
|
||||
Player player = context.sender();
|
||||
player.sendMessage("开始测试");
|
||||
NamespacedKey key = context.get("setTag");
|
||||
BlockTags.test(plugin().adapt(player), context.get("reset"), context.get("targetBlock"), key.asString());
|
||||
player.sendMessage("结束测试");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1790,7 +1790,7 @@ public class PacketConsumers {
|
||||
buf.writeLong(seed);
|
||||
}
|
||||
} else {
|
||||
Optional<Object> optionalSound = FastNMS.INSTANCE.method$BuiltInRegistries$byId(Reflections.instance$BuiltInRegistries$SOUND_EVENT, id - 1);
|
||||
Optional<Object> optionalSound = FastNMS.INSTANCE.method$IdMap$byId(Reflections.instance$BuiltInRegistries$SOUND_EVENT, id - 1);
|
||||
if (optionalSound.isEmpty()) return;
|
||||
Object soundEvent = optionalSound.get();
|
||||
Key soundId = Key.of(FastNMS.INSTANCE.method$SoundEvent$location(soundEvent));
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
package net.momirealms.craftengine.bukkit.util;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.momirealms.craftengine.bukkit.nms.FastNMS;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BlockTags {
|
||||
private static final Map<Key, Object> CACHE = new HashMap<>();
|
||||
@@ -31,80 +24,4 @@ public class BlockTags {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于测试下面的 buildFakeUpdateTagsPacket 方法
|
||||
*
|
||||
* @param player CraftEngine玩家对象
|
||||
* @param reset 是否重置标签
|
||||
* @param targetBlock 测试添加标签的目标方块
|
||||
* @param setTag 测试添加的标签
|
||||
*/
|
||||
public static void test(Player player, boolean reset, String targetBlock, String setTag) {
|
||||
Map<Object, Map<String, IntList>> addTags = new HashMap<>();
|
||||
if (!reset) {
|
||||
Object registries = Reflections.instance$BuiltInRegistries$BLOCK;
|
||||
Object key = FastNMS.INSTANCE.method$Registry$key(registries);
|
||||
Map<String, IntList> blockTags = new HashMap<>();
|
||||
IntList blockId = new IntArrayList();
|
||||
Object blockKey = KeyUtils.toResourceLocation(Key.of(targetBlock));
|
||||
Object block = FastNMS.INSTANCE.method$Registry$get(registries, blockKey);
|
||||
Optional<Integer> optionalBlockId = FastNMS.INSTANCE.method$BuiltInRegistries$getId(registries, block);
|
||||
optionalBlockId.ifPresent(integer -> blockId.add(integer.intValue()));
|
||||
blockTags.put(setTag, blockId);
|
||||
addTags.put(key, blockTags);
|
||||
}
|
||||
Object packet = buildFakeUpdateTagsPacket(addTags);
|
||||
player.sendPacket(packet, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建模拟标签更新数据包(用于向客户端添加虚拟标签)
|
||||
*
|
||||
* @param addTags 需要添加的标签数据,结构为嵌套映射:
|
||||
* <pre>{@code
|
||||
* Map结构示例:
|
||||
* {
|
||||
* 注册表键1 (如BuiltInRegistries.ITEM.key) -> {
|
||||
* "命名空间:值1" -> IntList.of(1, 2, 3), // 该命名空间下生效的物品ID列表
|
||||
* "命名空间:值2" -> IntList.of(5, 7)
|
||||
* },
|
||||
* 注册表键2 (如BuiltInRegistries.BLOCK.key) -> {
|
||||
* "minecraft:beacon_base_blocks" -> IntList.of(1024, 2048)
|
||||
* },
|
||||
* ....
|
||||
* }
|
||||
* }</pre>
|
||||
* 其中:</br>
|
||||
* - 外层键:注册表对象(如物品/方块注册表)</br>
|
||||
* - 中间层键:标签的命名空间:值(字符串)</br>
|
||||
* - 值:包含注册表内项目数字ID的IntList
|
||||
*
|
||||
* @return 可发送给客户端的 ClientboundUpdateTagsPacket 数据包对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Object buildFakeUpdateTagsPacket(Map<Object, Map<String, IntList>> addTags) {
|
||||
Map<Object, Object> registriesNetworkPayload = (Map<Object, Object>) FastNMS.INSTANCE.method$TagNetworkSerialization$serializeTagsToNetwork();
|
||||
for (Map.Entry<Object, Map<String, IntList>> entry : addTags.entrySet()) {
|
||||
Object registryKey = entry.getKey();
|
||||
Map<String, IntList> tagsToAdd = entry.getValue();
|
||||
Object existingPayload = registriesNetworkPayload.get(registryKey);
|
||||
if (existingPayload == null) continue;
|
||||
FriendlyByteBuf deserializeBuf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
FastNMS.INSTANCE.method$TagNetworkSerialization$NetworkPayload$write(existingPayload, deserializeBuf);
|
||||
Map<String, IntList> combinedTags = deserializeBuf.readMap(
|
||||
FriendlyByteBuf::readUtf,
|
||||
FriendlyByteBuf::readIntIdList
|
||||
);
|
||||
combinedTags.putAll(tagsToAdd);
|
||||
FriendlyByteBuf serializeBuf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
serializeBuf.writeMap(combinedTags,
|
||||
FriendlyByteBuf::writeUtf,
|
||||
FriendlyByteBuf::writeIntIdList
|
||||
);
|
||||
Object mergedPayload = FastNMS.INSTANCE.method$TagNetworkSerialization$NetworkPayload$read(serializeBuf);
|
||||
registriesNetworkPayload.put(registryKey, mergedPayload);
|
||||
}
|
||||
return FastNMS.INSTANCE.constructor$ClientboundUpdateTagsPacket(registriesNetworkPayload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,8 @@ public class KeyUtils {
|
||||
public static Object toResourceLocation(Key key) {
|
||||
return toResourceLocation(key.namespace(), key.value());
|
||||
}
|
||||
|
||||
public static NamespacedKey toNamespacedKey(Key key) {
|
||||
return new NamespacedKey(key.namespace(), key.value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package net.momirealms.craftengine.bukkit.util;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import net.momirealms.craftengine.bukkit.nms.FastNMS;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class TagUtils {
|
||||
|
||||
private TagUtils() {}
|
||||
|
||||
/**
|
||||
* 构建模拟标签更新数据包(用于向客户端添加虚拟标签)
|
||||
*
|
||||
* @param tags 需要添加的标签数据,结构为嵌套映射:
|
||||
* <pre>{@code
|
||||
* Map结构示例:
|
||||
* {
|
||||
* 注册表键1 (如BuiltInRegistries.ITEM.key) -> {
|
||||
* "命名空间:值1" -> IntList.of(1, 2, 3), // 该命名空间下生效的物品ID列表
|
||||
* "命名空间:值2" -> IntList.of(5, 7)
|
||||
* },
|
||||
* 注册表键2 (如BuiltInRegistries.BLOCK.key) -> {
|
||||
* "minecraft:beacon_base_blocks" -> IntList.of(1024, 2048)
|
||||
* },
|
||||
* ....
|
||||
* }
|
||||
* }</pre>
|
||||
* 其中:</br>
|
||||
* - 外层键:注册表ResourceKey</br>
|
||||
* - 中间层键:标签的命名空间:值(字符串)</br>
|
||||
* - 值:包含注册表内项目数字ID的IntList
|
||||
*
|
||||
* @return 可发送给客户端的 ClientboundUpdateTagsPacket 数据包对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Object createUpdateTagsPacket(Map<Object, List<TagEntry>> tags) {
|
||||
Map<Object, Object> registriesNetworkPayload = (Map<Object, Object>) FastNMS.INSTANCE.method$TagNetworkSerialization$serializeTagsToNetwork();
|
||||
Map<Object, Object> modified = new HashMap<>();
|
||||
for (Map.Entry<Object, List<TagEntry>> entry : tags.entrySet()) {
|
||||
Object existingPayload = registriesNetworkPayload.get(entry.getKey());
|
||||
if (existingPayload == null) continue;
|
||||
FriendlyByteBuf deserializeBuf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
FastNMS.INSTANCE.method$TagNetworkSerialization$NetworkPayload$write(existingPayload, deserializeBuf);
|
||||
Map<String, IntList> originalTags = deserializeBuf.readMap(
|
||||
FriendlyByteBuf::readUtf,
|
||||
FriendlyByteBuf::readIntIdList
|
||||
);
|
||||
Map<Integer, List<String>> reversedTags = new HashMap<>();
|
||||
for (Map.Entry<String, IntList> tagEntry : originalTags.entrySet()) {
|
||||
for (int id : tagEntry.getValue()) {
|
||||
reversedTags.computeIfAbsent(id, k -> new ArrayList<>()).add(tagEntry.getKey());
|
||||
}
|
||||
}
|
||||
for (TagEntry tagEntry : entry.getValue()) {
|
||||
reversedTags.remove(tagEntry.id);
|
||||
for (String tag : tagEntry.tags) {
|
||||
reversedTags.computeIfAbsent(tagEntry.id, k -> new ArrayList<>()).add(tag);
|
||||
}
|
||||
}
|
||||
Map<String, IntList> processedTags = new HashMap<>();
|
||||
for (Map.Entry<Integer, List<String>> tagEntry : reversedTags.entrySet()) {
|
||||
for (String tag : tagEntry.getValue()) {
|
||||
processedTags.computeIfAbsent(tag, k -> new IntArrayList()).addLast(tagEntry.getKey());
|
||||
}
|
||||
}
|
||||
FriendlyByteBuf serializeBuf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
serializeBuf.writeMap(processedTags,
|
||||
FriendlyByteBuf::writeUtf,
|
||||
FriendlyByteBuf::writeIntIdList
|
||||
);
|
||||
Object mergedPayload = FastNMS.INSTANCE.method$TagNetworkSerialization$NetworkPayload$read(serializeBuf);
|
||||
modified.put(entry.getKey(), mergedPayload);
|
||||
}
|
||||
return FastNMS.INSTANCE.constructor$ClientboundUpdateTagsPacket(modified);
|
||||
}
|
||||
|
||||
public record TagEntry(int id, List<String> tags) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user