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

feat(resource-pack): 实现资源包推送功能

This commit is contained in:
jhqwqmc
2025-04-17 05:26:04 +08:00
parent bdb0512764
commit a6ac53b9d8
12 changed files with 193 additions and 17 deletions

View File

@@ -2,10 +2,13 @@ package net.momirealms.craftengine.bukkit.pack;
import net.momirealms.craftengine.bukkit.api.event.AsyncResourcePackGenerateEvent;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.util.ResourcePackUtils;
import net.momirealms.craftengine.core.pack.AbstractPackManager;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.Bukkit;
@@ -18,6 +21,7 @@ import org.bukkit.event.player.PlayerResourcePackStatusEvent;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class BukkitPackManager extends AbstractPackManager implements Listener {
private final BukkitCraftEngine plugin;
@@ -53,6 +57,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
@Override
public void load() {
super.load();
if (Config.sendPackOnJoin()) {
this.modifyServerSettings();
}
@@ -64,9 +69,9 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
Object properties = Reflections.field$DedicatedServerSettings$properties.get(settings);
Object info;
if (VersionHelper.isVersionNewerThan1_20_3()) {
info = Reflections.constructor$ServerResourcePackInfo.newInstance(new UUID(0, 0), "", "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()));
info = Reflections.constructor$ServerResourcePackInfo.newInstance(new UUID(0, 0), "https://127.0.0.1:65536", "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()));
} else {
info = Reflections.constructor$ServerResourcePackInfo.newInstance(new UUID(0, 0), "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()));
info = Reflections.constructor$ServerResourcePackInfo.newInstance("https://127.0.0.1:65536", "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()));
}
Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.of(info));
} catch (Exception e) {
@@ -102,4 +107,23 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
// generate pack
super.generateResourcePack();
}
@EventHandler
public void onAsyncResourcePackGenerate(AsyncResourcePackGenerateEvent event) {
Bukkit.getOnlinePlayers().forEach(p -> {
BukkitServerPlayer user = this.plugin.adapt(p);
CompletableFuture<ResourcePackDownloadData> future = resourcePackHost().requestResourcePackDownloadLink(user.uuid());
if (future.isDone()) {
try {
ResourcePackDownloadData data = future.get();
user.sendPacket(ResourcePackUtils.createPacket(
data.uuid(), data.url(), data.sha1()
), true);
user.setCurrentResourcePackUUID(data.uuid());
} catch (Exception e) {
plugin.logger().warn("Failed to send resource pack to player " + p.getName(), e);
}
}
});
}
}

View File

@@ -145,6 +145,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
registerNMSPacketConsumer(PacketConsumers.SIGN_UPDATE, Reflections.clazz$ServerboundSignUpdatePacket);
registerNMSPacketConsumer(PacketConsumers.EDIT_BOOK, Reflections.clazz$ServerboundEditBookPacket);
registerNMSPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD, Reflections.clazz$ServerboundCustomPayloadPacket);
registerNMSPacketConsumer(PacketConsumers.RESOURCE_PACK_PUSH, Reflections.clazz$ClientboundResourcePackPushPacket);
registerByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket());
registerByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket());
registerByteBufPacketConsumer(VersionHelper.isVersionNewerThan1_21_3() ? PacketConsumers.LEVEL_PARTICLE_1_21_3 : (VersionHelper.isVersionNewerThan1_20_5() ? PacketConsumers.LEVEL_PARTICLE_1_20_5 : PacketConsumers.LEVEL_PARTICLE_1_20), this.packetIds.clientboundLevelParticlesPacket());

View File

@@ -21,11 +21,14 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult;
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.plugin.network.NetworkManager;
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.WorldEvents;
@@ -44,6 +47,8 @@ import org.bukkit.util.RayTraceResult;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
public class PacketConsumers {
@@ -51,6 +56,7 @@ public class PacketConsumers {
private static int[] mappingsMOD;
private static IntIdentityList BLOCK_LIST;
private static IntIdentityList BIOME_LIST;
private static final UUID EMPTY_UUID = new UUID(0, 0);
public static void init(Map<Integer, Integer> map, int registrySize) {
mappings = new int[registrySize];
@@ -1158,7 +1164,6 @@ public class PacketConsumers {
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> HELLO_C2S = (user, event, packet) -> {
try {
if (!user.isOnline()) return;
BukkitServerPlayer player = (BukkitServerPlayer) user;
String name = (String) Reflections.field$ServerboundHelloPacket$name.get(packet);
UUID uuid = (UUID) Reflections.field$ServerboundHelloPacket$uuid.get(packet);
@@ -2096,4 +2101,45 @@ public class PacketConsumers {
CraftEngine.instance().logger().warn("Failed to handle ClientboundSetScorePacket", e);
}
};
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> RESOURCE_PACK_PUSH = (user, event, packet) -> {
try {
if (!VersionHelper.isVersionNewerThan1_20_2()) return;
if (user.handleResourcePackPush()) return;
event.setCancelled(true);
if (VersionHelper.isVersionNewerThan1_20_3()) {
user.receivePacket(Reflections.constructor$ServerboundResourcePackPacket.newInstance(
EMPTY_UUID, Reflections.instance$ServerboundResourcePackPacket$Action$ACCEPTED
));
} else {
user.receivePacket(Reflections.constructor$ServerboundResourcePackPacket.newInstance(
Reflections.instance$ServerboundResourcePackPacket$Action$ACCEPTED
));
}
SchedulerTask timeoutTask = CraftEngine.instance().scheduler().asyncLater(() -> {
Thread.currentThread().interrupt();
}, 27, TimeUnit.SECONDS);
Object newPacket = packet;
out : try {
ResourcePackHost host = CraftEngine.instance().packManager().resourcePackHost();
CompletableFuture<ResourcePackDownloadData> future = host.requestResourcePackDownloadLink(user.uuid());
if (!future.isDone()) break out;
ResourcePackDownloadData data = future.get();
newPacket = ResourcePackUtils.createPacket(data.uuid(), data.url(), data.sha1());
user.setCurrentResourcePackUUID(data.uuid());
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to get resource pack url", e);
} finally {
timeoutTask.cancel();
}
if (!user.nettyChannel().isActive()) return;
user.setHandleResourcePackPush(true);
user.nettyChannel().writeAndFlush(newPacket);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ClientboundResourcePackPushPacket", e);
}
};
}

View File

@@ -75,6 +75,8 @@ public class BukkitServerPlayer extends Player {
private Key lastUsedRecipe = null;
// has fabric client mod or not
private boolean hasClientMod = false;
// resource pack
private boolean handleResourcePackPush = false;
// cache if player can break blocks
private boolean clientSideCanBreak = true;
// prevent AFK players from consuming too much CPU resource on predicting
@@ -754,6 +756,14 @@ public class BukkitServerPlayer extends Player {
return this.resourcePackUUID;
}
public boolean handleResourcePackPush() {
return this.handleResourcePackPush;
}
public void setHandleResourcePackPush(boolean handleResourcePackPush) {
this.handleResourcePackPush = handleResourcePackPush;
}
@Override
public void clearView() {
this.entityTypeView.clear();

View File

@@ -6440,8 +6440,8 @@ public class Reflections {
public static final Class<?> clazz$ServerboundHelloPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ServerboundHelloPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketLoginInStart")
BukkitReflectionUtils.assembleMCClass("network.protocol.login.ServerboundHelloPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.login.PacketLoginInStart")
)
);
@@ -6456,4 +6456,56 @@ public class Reflections {
clazz$ServerboundHelloPacket, UUID.class, 0
)
);
public static final Field field$ClientboundResourcePackPushPacket$id =
ReflectionUtils.getDeclaredField(
clazz$ClientboundResourcePackPushPacket, UUID.class, 0
);
public static final Field field$ClientboundResourcePackPushPacket$prompt = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$ClientboundResourcePackPushPacket,
VersionHelper.isVersionNewerThan1_20_5() ? Optional.class : clazz$Component,
0
)
);
public static final Class<?> clazz$ServerboundResourcePackPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundResourcePackPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayInResourcePackStatus")
)
);
public static final Class<?> clazz$ServerboundResourcePackPacket$Action = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundResourcePackPacket$Action"),
BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundResourcePackPacket$a"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ServerboundResourcePackPacket$Action"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayInResourcePackStatus$EnumResourcePackStatus")
)
);
public static final Method method$ServerboundResourcePackPacket$Action$values = requireNonNull(
ReflectionUtils.getStaticMethod(
clazz$ServerboundResourcePackPacket$Action, clazz$ServerboundResourcePackPacket$Action.arrayType()
)
);
public static final Object instance$ServerboundResourcePackPacket$Action$ACCEPTED;
static {
try {
Object[] values = (Object[]) method$ServerboundResourcePackPacket$Action$values.invoke(null);
instance$ServerboundResourcePackPacket$Action$ACCEPTED = values[3];
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static final Constructor<?> constructor$ServerboundResourcePackPacket = requireNonNull(
field$ClientboundResourcePackPushPacket$id != null
? ReflectionUtils.getConstructor(clazz$ServerboundResourcePackPacket, UUID.class, clazz$ServerboundResourcePackPacket$Action)
: ReflectionUtils.getConstructor(clazz$ServerboundResourcePackPacket, clazz$ServerboundResourcePackPacket$Action)
);
}

View File

@@ -0,0 +1,21 @@
package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.VersionHelper;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional;
import java.util.UUID;
public class ResourcePackUtils {
public static Object createPacket(UUID uuid, String url, String hash) throws InvocationTargetException, InstantiationException, IllegalAccessException {
if (VersionHelper.isVersionNewerThan1_20_5()) {
return Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(uuid, url, hash, Config.kickOnDeclined(), Optional.of(ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt())));
} else if (VersionHelper.isVersionNewerThan1_20_3()) {
return Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(uuid, url, hash, Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()));
} else {
return Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(url, hash, Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()));
}
}
}

View File

@@ -7,6 +7,8 @@ import net.momirealms.craftengine.core.font.BitmapImage;
import net.momirealms.craftengine.core.font.Font;
import net.momirealms.craftengine.core.item.EquipmentData;
import net.momirealms.craftengine.core.pack.conflict.resolution.ConditionalResolution;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.pack.host.impl.SelfHost;
import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration;
import net.momirealms.craftengine.core.pack.model.ItemModel;
import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel;
@@ -65,6 +67,7 @@ public abstract class AbstractPackManager implements PackManager {
private final Map<String, ConfigSectionParser> sectionParsers = new HashMap<>();
private final TreeMap<ConfigSectionParser, List<CachedConfig>> cachedConfigs = new TreeMap<>();
protected BiConsumer<Path, Path> zipGenerator;
protected ResourcePackHost resourcePackHost;
public AbstractPackManager(CraftEngine plugin, BiConsumer<Path, Path> eventDispatcher) {
this.plugin = plugin;
@@ -143,6 +146,15 @@ public abstract class AbstractPackManager implements PackManager {
@Override
public void load() {
this.resourcePackHost = new SelfHost(Config.hostIP(), Config.hostPort());
if (Files.exists(resourcePackPath())) {
this.resourcePackHost.upload(resourcePackPath());
}
}
@Override
public ResourcePackHost resourcePackHost() {
return this.resourcePackHost;
}
@Override
@@ -512,6 +524,7 @@ public abstract class AbstractPackManager implements PackManager {
long end = System.currentTimeMillis();
this.plugin.logger().info("Finished generating resource pack in " + (end - start) + "ms");
this.resourcePackHost.upload(zipFile);
this.eventDispatcher.accept(generatedPackPath, zipFile);
}

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.pack;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import org.jetbrains.annotations.NotNull;
@@ -33,4 +34,6 @@ public interface PackManager extends Manageable {
void generateResourcePack();
Path resourcePackPath();
ResourcePackHost resourcePackHost();
}

View File

@@ -16,7 +16,7 @@ public class SelfHost implements ResourcePackHost {
@Override
public CompletableFuture<ResourcePackDownloadData> requestResourcePackDownloadLink(UUID player) {
return CompletableFuture.completedFuture(SelfHostHttpServer.instance().generateOneTimeUrl(player));
return CompletableFuture.completedFuture(SelfHostHttpServer.instance().generateOneTimeUrl());
}
@Override

View File

@@ -9,7 +9,6 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.OutputStream;
@@ -23,7 +22,9 @@ import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class SelfHostHttpServer {
@@ -37,7 +38,7 @@ public class SelfHostHttpServer {
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
private final ExecutorService threadPool = Executors.newFixedThreadPool(1);
private ExecutorService threadPool;
private HttpServer server;
private final AtomicLong totalRequests = new AtomicLong();
@@ -57,7 +58,7 @@ public class SelfHostHttpServer {
}
@NotNull
public ResourcePackDownloadData generateOneTimeUrl(UUID player) {
public ResourcePackDownloadData generateOneTimeUrl() {
String token = UUID.randomUUID().toString();
this.oneTimePackUrls.put(token, true);
return new ResourcePackDownloadData(
@@ -102,11 +103,15 @@ public class SelfHostHttpServer {
}
public void updatePort(int port) {
if (port <= 0 || port > 65535) {
throw new IllegalArgumentException("Invalid port number: " + port);
}
if (port == this.port) return;
if (server != null) disable();
this.port = port;
try {
server = HttpServer.create(new InetSocketAddress("::", port), 0);
threadPool = Executors.newFixedThreadPool(1);
server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/download", new ResourcePackHandler());
server.createContext("/metrics", this::handleMetrics);
server.setExecutor(threadPool);
@@ -134,7 +139,9 @@ public class SelfHostHttpServer {
if (server != null) {
server.stop(0);
server = null;
threadPool.shutdownNow();
if (threadPool != null) {
threadPool.shutdownNow();
}
}
}

View File

@@ -19,11 +19,6 @@ public class SimpleExternalHost implements ResourcePackHost {
return CompletableFuture.completedFuture(this.downloadData);
}
@Override
public ResourcePackDownloadData getResourcePackDownloadLink(UUID player) {
return this.downloadData;
}
@Override
public CompletableFuture<Boolean> upload(Path resourcePackPath) {
return CompletableFuture.completedFuture(true);

View File

@@ -55,4 +55,8 @@ public interface NetWorkUser {
@Nullable
UUID currentResourcePackUUID();
boolean handleResourcePackPush();
void setHandleResourcePackPush(boolean handleFinishConfiguration);
}