From 71fbf7f7b97c6fd895bd6199ecb89df6e6427313 Mon Sep 17 00:00:00 2001 From: iqtester Date: Mon, 14 Apr 2025 18:08:14 +0800 Subject: [PATCH 01/62] feat: Re-implement WorldEdit command and suggestion support in a proper way --- .../worldedit/WorldEditBlockRegister.java | 98 ++++++++++++++++++- .../bukkit/block/BukkitBlockManager.java | 26 ++--- .../bukkit/block/WorldEditCommandHelper.java | 85 ---------------- 3 files changed, 110 insertions(+), 99 deletions(-) delete mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/WorldEditCommandHelper.java diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java index 6905c77c7..3f3766162 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java @@ -1,24 +1,116 @@ package net.momirealms.craftengine.bukkit.compatibility.worldedit; +import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitBlockRegistry; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.internal.registry.InputParser; import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.momirealms.craftengine.core.block.AbstractBlockManager; +import net.momirealms.craftengine.core.block.BlockStateParser; +import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ReflectionUtils; import org.bukkit.Material; import java.lang.reflect.Field; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; public class WorldEditBlockRegister { - private static final Field field$BlockType$blockMaterial; + private final Field field$BlockType$blockMaterial; + private final AbstractBlockManager manager; + private final Set cachedSuggestions = new HashSet<>(); - static { + public WorldEditBlockRegister(AbstractBlockManager manager) { field$BlockType$blockMaterial = ReflectionUtils.getDeclaredField(BlockType.class, "blockMaterial"); + this.manager = manager; } - public static void register(Key id) throws ReflectiveOperationException { + public void enable() { + CEBlockParser blockParser = new CEBlockParser(WorldEdit.getInstance()); + WorldEdit.getInstance().getBlockFactory().register(blockParser); + } + + public void load() { + Collection cachedSuggestions = manager.cachedSuggestions(); + for (Object o : cachedSuggestions) { + this.cachedSuggestions.add(o.toString()); + } + } + + public void unload() { + cachedSuggestions.clear(); + } + + public void register(Key id) throws ReflectiveOperationException { BlockType blockType = new BlockType(id.toString(), blockState -> blockState); field$BlockType$blockMaterial.set(blockType, LazyReference.from(() -> new BukkitBlockRegistry.BukkitBlockMaterial(null, Material.STONE))); BlockType.REGISTRY.register(id.toString(), blockType); } + + private class CEBlockParser extends InputParser { + + protected CEBlockParser(WorldEdit worldEdit) { + super(worldEdit); + } + + @Override + public Stream getSuggestions(String input) { + Set namespacesInUse = manager.namespacesInUse(); + + if (input.isEmpty() || input.equals(":")) { + return namespacesInUse.stream().map(namespace -> namespace + ":"); + } + + if (input.startsWith(":")) { + String term = input.substring(1).toLowerCase(); + return cachedSuggestions.stream().filter(s -> s.toLowerCase().contains(term)); + } + + if (!input.contains(":")) { + String lowerSearch = input.toLowerCase(); + return Stream.concat( + namespacesInUse.stream().filter(n -> n.startsWith(lowerSearch)).map(n -> n + ":"), + cachedSuggestions.stream().filter(s -> s.toLowerCase().startsWith(lowerSearch)) + ); + } + return cachedSuggestions.stream().filter(s -> s.toLowerCase().startsWith(input.toLowerCase())); + } + + @Override + public BaseBlock parseFromInput(String input, ParserContext context) { + int colonIndex = input.indexOf(':'); + if (colonIndex == -1) return null; + + Set namespacesInUse = manager.namespacesInUse(); + String namespace = input.substring(0, colonIndex); + if (!namespacesInUse.contains(namespace)) return null; + + ImmutableBlockState state = BlockStateParser.deserialize(input); + if (state == null) return null; + + try { + String id = state.customBlockState().handle().toString(); + int first = id.indexOf('{'); + int last = id.indexOf('}'); + if (first != -1 && last != -1 && last > first) { + String blockId = id.substring(first + 1, last); + BlockType blockType = BlockTypes.get(blockId); + if (blockType == null) { + return null; + } + return blockType.getDefaultState().toBaseBlock(); + } else { + throw new IllegalArgumentException("Invalid block ID format: " + id); + } + } catch (NullPointerException e) { + return null; + } + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 01b2c7495..894478dae 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -87,7 +87,8 @@ public class BukkitBlockManager extends AbstractBlockManager { // Event listeners private final BlockEventListener blockEventListener; private final FallingBlockRemoveListener fallingBlockRemoveListener; - private WorldEditCommandHelper weCommandHelper; + // WE support + private WorldEditBlockRegister weBlockRegister; public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin); @@ -128,18 +129,11 @@ public class BukkitBlockManager extends AbstractBlockManager { if (this.fallingBlockRemoveListener != null) { Bukkit.getPluginManager().registerEvents(this.fallingBlockRemoveListener, plugin.bootstrap()); } - boolean hasWE = false; // WorldEdit if (this.plugin.isPluginEnabled("FastAsyncWorldEdit")) { this.initFastAsyncWorldEditHook(); - hasWE = true; } else if (this.plugin.isPluginEnabled("WorldEdit")) { this.initWorldEditHook(); - hasWE = true; - } - if (hasWE) { - this.weCommandHelper = new WorldEditCommandHelper(this.plugin, this); - this.weCommandHelper.enable(); } } @@ -152,6 +146,8 @@ public class BukkitBlockManager extends AbstractBlockManager { this.modBlockStates.clear(); if (EmptyBlock.INSTANCE != null) Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.defaultState()); + if (weBlockRegister != null) + weBlockRegister.unload(); } @Override @@ -159,7 +155,6 @@ public class BukkitBlockManager extends AbstractBlockManager { this.unload(); HandlerList.unregisterAll(this.blockEventListener); if (this.fallingBlockRemoveListener != null) HandlerList.unregisterAll(this.fallingBlockRemoveListener); - if (this.weCommandHelper != null) this.weCommandHelper.disable(); } @Override @@ -172,6 +167,7 @@ public class BukkitBlockManager extends AbstractBlockManager { initSuggestions(); resetPacketConsumers(); clearCache(); + loadWorldEditRegister(); } private void clearCache() { @@ -181,19 +177,27 @@ public class BukkitBlockManager extends AbstractBlockManager { } public void initFastAsyncWorldEditHook() { - // do nothing + this.weBlockRegister = new WorldEditBlockRegister(this); + this.weBlockRegister.enable(); } public void initWorldEditHook() { + this.weBlockRegister = new WorldEditBlockRegister(this); + this.weBlockRegister.enable(); try { for (Key newBlockId : this.blockRegisterOrder) { - WorldEditBlockRegister.register(newBlockId); + weBlockRegister.register(newBlockId); } } catch (Exception e) { this.plugin.logger().warn("Failed to initialize world edit hook", e); } } + public void loadWorldEditRegister() { + if (this.weBlockRegister != null) + this.weBlockRegister.load(); + } + @Nullable public Object getMinecraftBlockHolder(int stateId) { return stateId2BlockHolder.get(stateId); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/WorldEditCommandHelper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/WorldEditCommandHelper.java deleted file mode 100644 index 88839c776..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/WorldEditCommandHelper.java +++ /dev/null @@ -1,85 +0,0 @@ -package net.momirealms.craftengine.bukkit.block; - -import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.core.block.BlockStateParser; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -// TODO A better command suggestion system -public class WorldEditCommandHelper implements Listener { - private final BukkitBlockManager manager; - private final BukkitCraftEngine plugin; - - public WorldEditCommandHelper(BukkitCraftEngine plugin, BukkitBlockManager manager) { - this.plugin = plugin; - this.manager = manager; - } - - public void enable() { - Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap()); - } - - public void disable() { - HandlerList.unregisterAll(this); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { - String message = event.getMessage(); - if (!message.startsWith("//")) return; - - Set cachedNamespaces = manager.namespacesInUse(); - String[] args = message.split(" "); - boolean modified = false; - - for (int i = 1; i < args.length; i++) { - String[] parts = args[i].split(","); - List processedParts = new ArrayList<>(parts.length); - boolean partModified = false; - - for (String part : parts) { - String processed = processIdentifier(part, cachedNamespaces); - partModified |= !part.equals(processed); - processedParts.add(processed); - } - - if (partModified) { - args[i] = String.join(",", processedParts); - modified = true; - } - } - - if (modified) { - event.setMessage(String.join(" ", args)); - } - } - - private String processIdentifier(String identifier, Set cachedNamespaces) { - int colonIndex = identifier.indexOf(':'); - if (colonIndex == -1) return identifier; - - String namespace = identifier.substring(0, colonIndex); - if (!cachedNamespaces.contains(namespace)) return identifier; - - ImmutableBlockState state = BlockStateParser.deserialize(identifier); - if (state == null) return identifier; - - try { - return BlockStateUtils.getBlockOwnerIdFromState( - state.customBlockState().handle() - ).toString(); - } catch (NullPointerException e) { - return identifier; - } - } -} From e2f414d4bf118b0d9c33c5982e76ca07b0e48d4a Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 16 Apr 2025 19:54:19 +0800 Subject: [PATCH 02/62] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E5=8C=85=E5=8F=91=E9=80=81=E6=97=A7=E6=9C=89=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/pack/BukkitPackManager.java | 161 +----------------- .../core/pack/AbstractPackManager.java | 9 - .../craftengine/core/pack/host/HostMode.java | 7 - .../core/pack/host/ResourcePackHost.java | 139 --------------- .../craftengine/core/plugin/CraftEngine.java | 2 - .../core/plugin/config/Config.java | 8 +- 6 files changed, 2 insertions(+), 324 deletions(-) delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/HostMode.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 6a875cb5a..a1adc36d1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -9,8 +9,6 @@ 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.core.pack.AbstractPackManager; -import net.momirealms.craftengine.core.pack.host.HostMode; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; @@ -32,8 +30,6 @@ import java.util.UUID; public class BukkitPackManager extends AbstractPackManager implements Listener { private final BukkitCraftEngine plugin; - private HostMode previousHostMode = HostMode.NONE; - private UUID previousHostUUID; public BukkitPackManager(BukkitCraftEngine plugin) { super(plugin, (rf, zp) -> { @@ -51,10 +47,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { @EventHandler(priority = EventPriority.LOW) public void onPlayerJoin(PlayerJoinEvent event) { - // for 1.20.1 servers, not recommended to use - if (Config.sendPackOnJoin() && !VersionHelper.isVersionNewerThan1_20_2()) { - this.sendResourcePack(plugin.networkManager().getUser(event.getPlayer()), null); - } + // todo 1.20.1 资源包发送 } @EventHandler(priority = EventPriority.LOW) @@ -70,56 +63,11 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { @Override public void load() { super.load(); - - // update server properties - if (VersionHelper.isVersionNewerThan1_20_2()) { - if (Config.hostMode() == HostMode.SELF_HOST) { - if (Files.exists(resourcePackPath())) { - updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, Config.kickOnDeclined(), Config.resourcePackPrompt()); - } - } else if (Config.hostMode() == HostMode.EXTERNAL_HOST) { - updateResourcePackSettings(Config.externalPackUUID(), Config.externalPackUrl(), Config.externalPackSha1(), Config.kickOnDeclined(), Config.resourcePackPrompt()); - } - } - - if (Config.sendPackOnReload()) { - if (this.previousHostMode == HostMode.SELF_HOST) { - this.previousHostUUID = super.packUUID; - } - // unload packs if user changed to none host - if (Config.hostMode() == HostMode.NONE && this.previousHostMode != HostMode.NONE) { - unloadResourcePackForOnlinePlayers(this.previousHostUUID); - } - // load new external resource pack on reload - if (Config.hostMode() == HostMode.EXTERNAL_HOST) { - if (this.previousHostMode == HostMode.NONE) { - updateResourcePackForOnlinePlayers(null); - } else { - updateResourcePackForOnlinePlayers(this.previousHostUUID); - } - // record previous host uuid here - this.previousHostUUID = Config.externalPackUUID(); - } - if (Config.hostMode() == HostMode.SELF_HOST && this.previousHostMode != HostMode.SELF_HOST) { - if (ReloadCommand.RELOAD_PACK_FLAG) { - ReloadCommand.RELOAD_PACK_FLAG = false; - if (this.previousHostMode == HostMode.NONE) { - updateResourcePackForOnlinePlayers(null); - } else if (this.previousHostMode == HostMode.EXTERNAL_HOST) { - updateResourcePackForOnlinePlayers(this.previousHostUUID); - } - } - } - } - this.previousHostMode = Config.hostMode(); } @Override public void unload() { super.unload(); - if (VersionHelper.isVersionNewerThan1_20_2() && this.previousHostMode != HostMode.NONE) { - resetResourcePackSettings(); - } } @Override @@ -132,112 +80,5 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { public void generateResourcePack() { // generate pack super.generateResourcePack(); - // update server properties - if (VersionHelper.isVersionNewerThan1_20_2()) { - if (Config.hostMode() == HostMode.SELF_HOST) { - updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, Config.kickOnDeclined(), Config.resourcePackPrompt()); - } - } - // resend packs - if (Config.hostMode() == HostMode.SELF_HOST && Config.sendPackOnReload()) { - updateResourcePackForOnlinePlayers(this.previousHostUUID); - } - } - - protected void updateResourcePackForOnlinePlayers(UUID previousUUID) { - for (Player player : Bukkit.getOnlinePlayers()) { - BukkitServerPlayer serverPlayer = plugin.adapt(player); - sendResourcePack(serverPlayer, previousUUID); - } - } - - private void resetResourcePackSettings() { - try { - Object settings = Reflections.field$DedicatedServer$settings.get(Reflections.method$MinecraftServer$getServer.invoke(null)); - Object properties = Reflections.field$DedicatedServerSettings$properties.get(settings); - Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.empty()); - } catch (Exception e) { - this.plugin.logger().warn("Failed to update resource pack settings", e); - } - } - - private void updateResourcePackSettings(UUID uuid, String url, String sha1, boolean required, Component prompt) { - if (!Config.sendPackOnJoin()) { - resetResourcePackSettings(); - return; - } - try { - Object settings = Reflections.field$DedicatedServer$settings.get(Reflections.method$MinecraftServer$getServer.invoke(null)); - Object properties = Reflections.field$DedicatedServerSettings$properties.get(settings); - Object info; - if (VersionHelper.isVersionNewerThan1_20_3()) { - info = Reflections.constructor$ServerResourcePackInfo.newInstance(uuid, url, sha1, required, ComponentUtils.adventureToMinecraft(prompt)); - } else { - info = Reflections.constructor$ServerResourcePackInfo.newInstance(url + uuid, sha1, required, ComponentUtils.adventureToMinecraft(prompt)); - } - Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.of(info)); - } catch (Exception e) { - this.plugin.logger().warn("Failed to update resource pack settings", e); - } - } - - public void sendResourcePack(NetWorkUser user, @Nullable UUID previousPack) { - if (Config.hostMode() == HostMode.NONE) return; - String url; - String sha1; - UUID uuid; - if (Config.hostMode() == HostMode.SELF_HOST) { - url = ResourcePackHost.instance().url(); - sha1 = super.packHash; - uuid = super.packUUID; - if (!Files.exists(resourcePackPath())) return; - } else { - url = Config.externalPackUrl(); - sha1 = Config.externalPackSha1(); - uuid = Config.externalPackUUID(); - if (uuid.equals(previousPack)) return; - } - - Object packPrompt = ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt()); - try { - Object packPacket; - if (VersionHelper.isVersionNewerThan1_20_5()) { - packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance( - uuid, url, sha1, Config.kickOnDeclined(), Optional.of(packPrompt) - ); - } else if (VersionHelper.isVersionNewerThan1_20_3()) { - packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance( - uuid, url, sha1, Config.kickOnDeclined(), packPrompt - ); - } else { - packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance( - url + uuid, sha1, Config.kickOnDeclined(), packPrompt - ); - } - if (user.decoderState() == ConnectionState.PLAY) { - if (previousPack != null && VersionHelper.isVersionNewerThan1_20_3()) { - plugin.networkManager().sendPackets(user, List.of(Reflections.constructor$ClientboundResourcePackPopPacket.newInstance(Optional.of(previousPack)), packPacket)); - } else { - user.sendPacket(packPacket, false); - } - } else { - user.nettyChannel().writeAndFlush(packPacket); - } - } catch (Exception e) { - this.plugin.logger().warn("Failed to send resource pack", e); - } - } - - public void unloadResourcePackForOnlinePlayers(UUID uuid) { - try { - for (Player player : Bukkit.getOnlinePlayers()) { - BukkitServerPlayer serverPlayer = plugin.adapt(player); - if (serverPlayer.decoderState() == ConnectionState.PLAY) { - serverPlayer.sendPacket(Reflections.constructor$ClientboundResourcePackPopPacket.newInstance(Optional.of(uuid)), true); - } - } - } catch (Exception e) { - this.plugin.logger().warn("Failed to unload online player resource pack", e); - } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index b8ff6f265..08e816388 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -7,8 +7,6 @@ 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.HostMode; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; import net.momirealms.craftengine.core.pack.model.ItemModel; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; @@ -151,13 +149,6 @@ public abstract class AbstractPackManager implements PackManager { @Override public void load() { this.calculateHash(); - if (Config.hostMode() == HostMode.SELF_HOST) { - Path path = Config.hostResourcePackPath().startsWith(".") ? plugin.dataFolderPath().resolve(Config.hostResourcePackPath()) : Path.of(Config.hostResourcePackPath()); - ResourcePackHost.instance().enable(Config.hostIP(), Config.hostPort(), path); - ResourcePackHost.instance().setRateLimit(Config.requestRate(), Config.requestInterval(), TimeUnit.SECONDS); - } else { - ResourcePackHost.instance().disable(); - } } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/HostMode.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/HostMode.java deleted file mode 100644 index aade43875..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/HostMode.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.momirealms.craftengine.core.pack.host; - -public enum HostMode { - SELF_HOST, - EXTERNAL_HOST, - NONE -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java deleted file mode 100644 index be9052f45..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java +++ /dev/null @@ -1,139 +0,0 @@ -package net.momirealms.craftengine.core.pack.host; - -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; -import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.config.Config; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -public class ResourcePackHost { - private static ResourcePackHost instance; - private HttpServer server; - private String ip; - private int port; - private Path resourcePackPath; - private final ConcurrentHashMap ipAccessMap = new ConcurrentHashMap<>(); - private int rateLimit = 1; - private long rateLimitInterval = 1000; - - public String url() { - return Config.hostProtocol() + "://" + ip + ":" + port + "/"; - } - - public void enable(String ip, int port, Path resourcePackPath) { - if (isAlive() && ip.equals(this.ip) && port == this.port && resourcePackPath.equals(this.resourcePackPath)) { - return; - } - if (server != null) { - disable(); - } - this.ip = ip; - this.port = port; - this.resourcePackPath = resourcePackPath; - - try { - server = HttpServer.create(new InetSocketAddress("::", port), 0); - server.createContext("/", new ResourcePackHandler()); - server.setExecutor(Executors.newCachedThreadPool()); - server.start(); - CraftEngine.instance().logger().info("HTTP resource pack server running on " + ip + ":" + port); - } catch (IOException e) { - CraftEngine.instance().logger().warn("Failed to start HTTP server", e); - } - } - - public void disable() { - if (server != null) { - server.stop(0); - server = null; - } - } - - public boolean isAlive() { - return server != null; - } - - public static ResourcePackHost instance() { - if (instance == null) { - instance = new ResourcePackHost(); - } - return instance; - } - - public void setRateLimit(int rateLimit, long rateLimitInterval, TimeUnit timeUnit) { - this.rateLimit = rateLimit; - this.rateLimitInterval = timeUnit.toMillis(rateLimitInterval); - } - - private class ResourcePackHandler implements HttpHandler { - @Override - public void handle(HttpExchange exchange) throws IOException { - if (Config.denyNonMinecraftRequest()) { - String userAgent = exchange.getRequestHeaders().getFirst("User-Agent"); - if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) { - CraftEngine.instance().debug(() -> "Blocked non-Minecraft Java client. User-Agent: " + userAgent); - sendError(exchange, 403); - return; - } - } - - String clientIp = exchange.getRemoteAddress().getAddress().getHostAddress(); - - IpAccessRecord record = ipAccessMap.compute(clientIp, (k, v) -> { - long currentTime = System.currentTimeMillis(); - if (v == null || currentTime - v.lastAccessTime > rateLimitInterval) { - return new IpAccessRecord(currentTime, 1); - } else { - v.accessCount++; - return v; - } - }); - - if (record.accessCount > rateLimit) { - CraftEngine.instance().debug(() -> "Rate limit exceeded for IP: " + clientIp); - sendError(exchange, 429); - return; - } - - if (!Files.exists(resourcePackPath)) { - CraftEngine.instance().logger().warn("ResourcePack not found: " + resourcePackPath); - sendError(exchange, 404); - return; - } - - exchange.getResponseHeaders().set("Content-Type", "application/zip"); - exchange.getResponseHeaders().set("Content-Length", String.valueOf(Files.size(resourcePackPath))); - exchange.sendResponseHeaders(200, Files.size(resourcePackPath)); - - try (OutputStream os = exchange.getResponseBody()) { - Files.copy(resourcePackPath, os); - } catch (IOException e) { - CraftEngine.instance().logger().warn("Failed to send pack", e); - } - } - - private void sendError(HttpExchange exchange, int code) throws IOException { - exchange.sendResponseHeaders(code, 0); - exchange.getResponseBody().close(); - } - } - - private static class IpAccessRecord { - long lastAccessTime; - int accessCount; - - IpAccessRecord(long lastAccessTime, int accessCount) { - this.lastAccessTime = lastAccessTime; - this.accessCount = accessCount; - } - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 0987a2c35..8f20802bf 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -7,7 +7,6 @@ import net.momirealms.craftengine.core.item.ItemManager; import net.momirealms.craftengine.core.item.recipe.RecipeManager; import net.momirealms.craftengine.core.loot.VanillaLootManager; import net.momirealms.craftengine.core.pack.PackManager; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory; @@ -243,7 +242,6 @@ public abstract class CraftEngine implements Plugin { if (this.commandManager != null) this.commandManager.unregisterFeatures(); if (this.senderFactory != null) this.senderFactory.close(); if (this.dependencyManager != null) this.dependencyManager.close(); - ResourcePackHost.instance().disable(); } protected void registerDefaultParsers() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 777cae1d3..b85fcb74d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -13,7 +13,6 @@ import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; import dev.dejvokep.boostedyaml.utils.format.NodeRole; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.pack.conflict.resolution.ConditionalResolution; -import net.momirealms.craftengine.core.pack.host.HostMode; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.PluginProperties; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; @@ -79,7 +78,6 @@ public class Config { protected float resource_pack$supported_version$min; protected float resource_pack$supported_version$max; - protected HostMode resource_pack$send$mode; protected boolean resource_pack$send$kick_if_declined; protected boolean resource_pack$send$send_on_join; protected boolean resource_pack$send$send_on_reload; @@ -215,7 +213,7 @@ public class Config { resource_pack$supported_version$min = getVersion(config.get("resource-pack.supported-version.min", "1.20").toString()); resource_pack$supported_version$max = getVersion(config.get("resource-pack.supported-version.max", "LATEST").toString()); resource_pack$merge_external_folders = config.getStringList("resource-pack.merge-external-folders"); - resource_pack$send$mode = HostMode.valueOf(config.getString("resource-pack.send.mode", "self-host").replace("-", "_").toUpperCase(Locale.ENGLISH)); + //resource_pack$send$mode = HostMode.valueOf(config.getString("resource-pack.send.mode", "self-host").replace("-", "_").toUpperCase(Locale.ENGLISH)); resource_pack$send$self_host$port = config.getInt("resource-pack.send.self-host.port", 8163); resource_pack$send$self_host$ip = config.getString("resource-pack.send.self-host.ip", "localhost"); resource_pack$self_host$local_file_path = config.getString("resource-pack.send.self-host.local-file-path", "./generated/resource_pack.zip"); @@ -459,10 +457,6 @@ public class Config { return instance.resource_pack$merge_external_folders; } - public static HostMode hostMode() { - return instance.resource_pack$send$mode; - } - public static String hostIP() { return instance.resource_pack$send$self_host$ip; } From 6dd9fdb1b09e0114b63ea55325720b8a99772b5c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 16 Apr 2025 20:10:25 +0800 Subject: [PATCH 03/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=BE=E7=BD=AEuuid?= =?UTF-8?q?=E5=92=8Cname?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/network/BukkitNetworkManager.java | 1 + .../plugin/network/PacketConsumers.java | 13 ++++++++++ .../plugin/user/BukkitServerPlayer.java | 25 ++++++++++++++++--- .../craftengine/bukkit/util/Reflections.java | 19 ++++++++++++++ .../pack/host/ResourcePackDownloadData.java | 6 +++++ .../core/pack/host/ResourcePackHost.java | 11 ++++++++ .../core/plugin/network/NetWorkUser.java | 7 ++++++ 7 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 02f13cf9b..e3c8c0a48 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -130,6 +130,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes registerNMSPacketConsumer(PacketConsumers.PLAYER_INFO_UPDATE, Reflections.clazz$ClientboundPlayerInfoUpdatePacket); registerNMSPacketConsumer(PacketConsumers.PLAYER_ACTION, Reflections.clazz$ServerboundPlayerActionPacket); registerNMSPacketConsumer(PacketConsumers.SWING_HAND, Reflections.clazz$ServerboundSwingPacket); + registerNMSPacketConsumer(PacketConsumers.HELLO_C2S, Reflections.clazz$ServerboundHelloPacket); registerNMSPacketConsumer(PacketConsumers.USE_ITEM_ON, Reflections.clazz$ServerboundUseItemOnPacket); registerNMSPacketConsumer(PacketConsumers.PICK_ITEM_FROM_BLOCK, Reflections.clazz$ServerboundPickItemFromBlockPacket); registerNMSPacketConsumer(PacketConsumers.SET_CREATIVE_SLOT, Reflections.clazz$ServerboundSetCreativeModeSlotPacket); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index ec32770cb..4086b34fb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -1156,6 +1156,19 @@ public class PacketConsumers { } } + public static final TriConsumer 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); + player.setName(name); + player.setUUID(uuid); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundHelloPacket", e); + } + }; + public static final TriConsumer SWING_HAND = (user, event, packet) -> { try { if (!user.isOnline()) return; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 1fe42faf8..69d2848a0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -43,6 +43,8 @@ public class BukkitServerPlayer extends Player { private final BukkitCraftEngine plugin; // connection state private final Channel channel; + private String name; + private UUID uuid; private ConnectionState decoderState; private ConnectionState encoderState; // some references @@ -94,6 +96,8 @@ public class BukkitServerPlayer extends Player { public void setPlayer(org.bukkit.entity.Player player) { this.playerRef = new WeakReference<>(player); this.serverPlayerRef = new WeakReference<>(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)); + this.uuid = player.getUniqueId(); + this.name = player.getName(); if (Reflections.method$CraftPlayer$setSimplifyContainerDesyncCheck != null) { try { Reflections.method$CraftPlayer$setSimplifyContainerDesyncCheck.invoke(player, true); @@ -219,9 +223,24 @@ public class BukkitServerPlayer extends Player { @Override public String name() { - org.bukkit.entity.Player player = platformPlayer(); - if (player == null) return "Unknown"; - return player.getName(); + return this.name; + } + + @Override + public void setName(String name) { + if (this.name != null) return; + this.name = name; + } + + @Override + public UUID uuid() { + return this.uuid; + } + + @Override + public void setUUID(UUID uuid) { + if (this.uuid != null) return; + this.uuid = uuid; } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index 0ce455beb..c27611440 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -6437,4 +6437,23 @@ public class Reflections { ReflectionUtils.getMethod( clazz$CraftPlayer, new String[]{"setSimplifyContainerDesyncCheck"}, boolean.class ); + + public static final Class clazz$ServerboundHelloPacket = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.game.ServerboundHelloPacket"), + BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketLoginInStart") + ) + ); + + public static final Field field$ServerboundHelloPacket$name = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$ServerboundHelloPacket, String.class, 0 + ) + ); + + public static final Field field$ServerboundHelloPacket$uuid = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$ServerboundHelloPacket, UUID.class, 0 + ) + ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java new file mode 100644 index 000000000..139b42814 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.pack.host; + +import java.util.UUID; + +public record ResourcePackDownloadData(String url, UUID uuid, String sha1) { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java new file mode 100644 index 000000000..0490d77c7 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java @@ -0,0 +1,11 @@ +package net.momirealms.craftengine.core.pack.host; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public interface ResourcePackHost { + + CompletableFuture requestOneTimeUrl(UUID player); + + ResourcePackDownloadData getResourcePackUrl(UUID player); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index b98e6f169..e79b0e842 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.ApiStatus; import java.util.List; import java.util.Map; +import java.util.UUID; public interface NetWorkUser { boolean isOnline(); @@ -17,6 +18,12 @@ public interface NetWorkUser { String name(); + void setName(String name); + + UUID uuid(); + + void setUUID(UUID uuid); + void sendPacket(Object packet, boolean immediately); void receivePacket(Object packet); From a33721446f9fc11d8d8aba29f5f7ca7349f7b994 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 16 Apr 2025 20:25:23 +0800 Subject: [PATCH 04/62] =?UTF-8?q?=E6=B5=85=E6=B5=85=E6=94=BE=E4=B8=A4?= =?UTF-8?q?=E4=B8=AA=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/item/BukkitItemManager.java | 2 +- .../core/item/AbstractItemManager.java | 2 +- .../craftengine/core/item/ItemManager.java | 2 +- .../core/pack/AbstractPackManager.java | 2 +- .../core/pack/host/ResourcePackHost.java | 4 +- .../core/pack/host/impl/SelfHost.java | 20 +++ .../pack/host/impl/SelfHostHttpServer.java | 139 ++++++++++++++++++ .../pack/host/impl/SimpleExternalHost.java | 25 ++++ .../{ => model}/LegacyOverridesModel.java | 2 +- .../core/plugin/network/NetWorkUser.java | 2 + 10 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java rename core/src/main/java/net/momirealms/craftengine/core/pack/{ => model}/LegacyOverridesModel.java (98%) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java index 0cafce423..1eeefc72a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java @@ -20,7 +20,7 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehaviors; import net.momirealms.craftengine.core.item.modifier.CustomModelDataModifier; import net.momirealms.craftengine.core.item.modifier.IdModifier; import net.momirealms.craftengine.core.item.modifier.ItemModelModifier; -import net.momirealms.craftengine.core.pack.LegacyOverridesModel; +import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index bcc2ffa4a..6e286da07 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.modifier.*; -import net.momirealms.craftengine.core.pack.LegacyOverridesModel; +import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; import net.momirealms.craftengine.core.pack.model.ItemModel; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java index 7b6ed87a1..2a9e464e4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java @@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; -import net.momirealms.craftengine.core.pack.LegacyOverridesModel; +import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; import net.momirealms.craftengine.core.pack.model.ItemModel; import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 08e816388..1d5b4f01b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.item.EquipmentData; import net.momirealms.craftengine.core.pack.conflict.resolution.ConditionalResolution; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; import net.momirealms.craftengine.core.pack.model.ItemModel; +import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator; import net.momirealms.craftengine.core.pack.obfuscation.ObfA; @@ -35,7 +36,6 @@ import java.nio.file.attribute.BasicFileAttributes; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; -import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java index 0490d77c7..13beaf412 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java @@ -5,7 +5,7 @@ import java.util.concurrent.CompletableFuture; public interface ResourcePackHost { - CompletableFuture requestOneTimeUrl(UUID player); + CompletableFuture requestResourcePackDownloadLink(UUID player); - ResourcePackDownloadData getResourcePackUrl(UUID player); + ResourcePackDownloadData getResourcePackDownloadLink(UUID player); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java new file mode 100644 index 000000000..aabe7a017 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -0,0 +1,20 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class SelfHost implements ResourcePackHost { + + @Override + public CompletableFuture requestResourcePackDownloadLink(UUID player) { + return null; + } + + @Override + public ResourcePackDownloadData getResourcePackDownloadLink(UUID player) { + return null; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java new file mode 100644 index 000000000..4722a0d2a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -0,0 +1,139 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class SelfHostHttpServer { + private static SelfHostHttpServer instance; + private HttpServer server; + private String ip; + private int port; + private Path resourcePackPath; + private final ConcurrentHashMap ipAccessMap = new ConcurrentHashMap<>(); + private int rateLimit = 1; + private long rateLimitInterval = 1000; + + public String url() { + return Config.hostProtocol() + "://" + ip + ":" + port + "/"; + } + + public void enable(String ip, int port, Path resourcePackPath) { + if (isAlive() && ip.equals(this.ip) && port == this.port && resourcePackPath.equals(this.resourcePackPath)) { + return; + } + if (server != null) { + disable(); + } + this.ip = ip; + this.port = port; + this.resourcePackPath = resourcePackPath; + + try { + server = HttpServer.create(new InetSocketAddress("::", port), 0); + server.createContext("/", new ResourcePackHandler()); + server.setExecutor(Executors.newCachedThreadPool()); + server.start(); + CraftEngine.instance().logger().info("HTTP resource pack server running on " + ip + ":" + port); + } catch (IOException e) { + CraftEngine.instance().logger().warn("Failed to start HTTP server", e); + } + } + + public void disable() { + if (server != null) { + server.stop(0); + server = null; + } + } + + public boolean isAlive() { + return server != null; + } + + public static SelfHostHttpServer instance() { + if (instance == null) { + instance = new SelfHostHttpServer(); + } + return instance; + } + + public void setRateLimit(int rateLimit, long rateLimitInterval, TimeUnit timeUnit) { + this.rateLimit = rateLimit; + this.rateLimitInterval = timeUnit.toMillis(rateLimitInterval); + } + + private class ResourcePackHandler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) throws IOException { + if (Config.denyNonMinecraftRequest()) { + String userAgent = exchange.getRequestHeaders().getFirst("User-Agent"); + if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) { + CraftEngine.instance().debug(() -> "Blocked non-Minecraft Java client. User-Agent: " + userAgent); + sendError(exchange, 403); + return; + } + } + + String clientIp = exchange.getRemoteAddress().getAddress().getHostAddress(); + + IpAccessRecord record = ipAccessMap.compute(clientIp, (k, v) -> { + long currentTime = System.currentTimeMillis(); + if (v == null || currentTime - v.lastAccessTime > rateLimitInterval) { + return new IpAccessRecord(currentTime, 1); + } else { + v.accessCount++; + return v; + } + }); + + if (record.accessCount > rateLimit) { + CraftEngine.instance().debug(() -> "Rate limit exceeded for IP: " + clientIp); + sendError(exchange, 429); + return; + } + + if (!Files.exists(resourcePackPath)) { + CraftEngine.instance().logger().warn("ResourcePack not found: " + resourcePackPath); + sendError(exchange, 404); + return; + } + + exchange.getResponseHeaders().set("Content-Type", "application/zip"); + exchange.getResponseHeaders().set("Content-Length", String.valueOf(Files.size(resourcePackPath))); + exchange.sendResponseHeaders(200, Files.size(resourcePackPath)); + + try (OutputStream os = exchange.getResponseBody()) { + Files.copy(resourcePackPath, os); + } catch (IOException e) { + CraftEngine.instance().logger().warn("Failed to send pack", e); + } + } + + private void sendError(HttpExchange exchange, int code) throws IOException { + exchange.sendResponseHeaders(code, 0); + exchange.getResponseBody().close(); + } + } + + private static class IpAccessRecord { + long lastAccessTime; + int accessCount; + + IpAccessRecord(long lastAccessTime, int accessCount) { + this.lastAccessTime = lastAccessTime; + this.accessCount = accessCount; + } + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java new file mode 100644 index 000000000..a021836bd --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java @@ -0,0 +1,25 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class SimpleExternalHost implements ResourcePackHost { + private final ResourcePackDownloadData downloadData; + + public SimpleExternalHost(ResourcePackDownloadData downloadData) { + this.downloadData = downloadData; + } + + @Override + public CompletableFuture requestResourcePackDownloadLink(UUID player) { + return CompletableFuture.completedFuture(this.downloadData); + } + + @Override + public ResourcePackDownloadData getResourcePackDownloadLink(UUID player) { + return this.downloadData; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/LegacyOverridesModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java similarity index 98% rename from core/src/main/java/net/momirealms/craftengine/core/pack/LegacyOverridesModel.java rename to core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java index 4d39a7d3e..07d6d1e7f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/LegacyOverridesModel.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.pack; +package net.momirealms.craftengine.core.pack.model; import com.google.gson.JsonObject; import org.jetbrains.annotations.NotNull; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index e79b0e842..2bf8b725c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -49,4 +49,6 @@ public interface NetWorkUser { boolean clientModEnabled(); void setClientModState(boolean enable); + + } From 244e65f12c66a3fa09b6d0e1128b6f4d4e03a539 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 16 Apr 2025 20:33:11 +0800 Subject: [PATCH 05/62] =?UTF-8?q?=E6=B5=85=E6=B5=85=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E4=B9=8B=E5=89=8D=E8=B5=84=E6=BA=90=E5=8C=85?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/user/BukkitServerPlayer.java | 24 +++++++++++++++++++ .../core/entity/player/Player.java | 2 ++ .../core/plugin/network/NetWorkUser.java | 6 ++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 69d2848a0..d70c984b7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -36,6 +36,7 @@ import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -47,6 +48,7 @@ public class BukkitServerPlayer extends Player { private UUID uuid; private ConnectionState decoderState; private ConnectionState encoderState; + private UUID resourcePackUUID; // some references private Reference playerRef; private Reference serverPlayerRef; @@ -742,9 +744,31 @@ public class BukkitServerPlayer extends Player { this.hasClientMod = enable; } + @Override + public void setCurrentResourcePackUUID(UUID uuid) { + this.resourcePackUUID = uuid; + } + + @Override + public @Nullable UUID currentResourcePackUUID() { + return this.resourcePackUUID; + } + @Override public void clearView() { this.entityTypeView.clear(); this.furnitureView.clear(); } + + @Override + public void unloadCurrentResourcePack() { + if (decoderState() == ConnectionState.PLAY && this.resourcePackUUID != null && VersionHelper.isVersionNewerThan1_20_3()) { + try { + sendPacket(Reflections.constructor$ClientboundResourcePackPopPacket.newInstance(Optional.of(this.resourcePackUUID)), true); + this.resourcePackUUID = null; + } catch (ReflectiveOperationException e) { + this.plugin.logger().warn("Failed to unload resource pack for player " + name()); + } + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index c0814beaf..0af8d0e34 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -82,4 +82,6 @@ public abstract class Player extends Entity implements NetWorkUser { public abstract void closeInventory(); public abstract void clearView(); + + public abstract void unloadCurrentResourcePack(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 2bf8b725c..2c1ea4b94 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -4,6 +4,7 @@ import io.netty.channel.Channel; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; @@ -50,5 +51,8 @@ public interface NetWorkUser { void setClientModState(boolean enable); - + void setCurrentResourcePackUUID(UUID uuid); + + @Nullable + UUID currentResourcePackUUID(); } From 716efea8a716e88f22de5ee12d5719617c1cb166 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 17 Apr 2025 02:19:52 +0800 Subject: [PATCH 06/62] =?UTF-8?q?=E5=87=86=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/item/BukkitItemManager.java | 1 - .../bukkit/pack/BukkitPackManager.java | 14 ---- .../core/item/AbstractItemManager.java | 2 +- .../craftengine/core/item/ItemManager.java | 2 +- .../core/pack/AbstractPackManager.java | 38 ---------- .../core/pack/host/ResourcePackHost.java | 3 + .../core/pack/host/impl/SelfHost.java | 6 ++ .../pack/host/impl/SelfHostHttpServer.java | 70 ++++++++++++++++--- .../core/plugin/config/Config.java | 5 +- .../craftengine/mod/CraftEnginePlugin.java | 4 -- 10 files changed, 76 insertions(+), 69 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java index 1eeefc72a..f8a5e1a93 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java @@ -20,7 +20,6 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehaviors; import net.momirealms.craftengine.core.item.modifier.CustomModelDataModifier; import net.momirealms.craftengine.core.item.modifier.IdModifier; import net.momirealms.craftengine.core.item.modifier.ItemModelModifier; -import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index a1adc36d1..896db1452 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -1,32 +1,18 @@ package net.momirealms.craftengine.bukkit.pack; -import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.api.event.AsyncResourcePackGenerateEvent; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.plugin.command.feature.ReloadCommand; -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.core.pack.AbstractPackManager; 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.util.VersionHelper; import org.bukkit.Bukkit; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerResourcePackStatusEvent; -import org.jetbrains.annotations.Nullable; - -import java.nio.file.Files; -import java.util.List; -import java.util.Optional; -import java.util.UUID; public class BukkitPackManager extends AbstractPackManager implements Listener { private final BukkitCraftEngine plugin; diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index 6e286da07..5bb33e20b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -2,9 +2,9 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.modifier.*; -import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; import net.momirealms.craftengine.core.pack.model.ItemModel; +import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.registry.Holder; diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java index 2a9e464e4..02cc0aade 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java @@ -2,9 +2,9 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; -import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; import net.momirealms.craftengine.core.pack.model.ItemModel; +import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 1d5b4f01b..b223f2e11 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -33,8 +33,6 @@ import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -66,10 +64,7 @@ public abstract class AbstractPackManager implements PackManager { private final Map loadedPacks = new HashMap<>(); private final Map sectionParsers = new HashMap<>(); private final TreeMap> cachedConfigs = new TreeMap<>(); - protected BiConsumer zipGenerator; - protected String packHash; - protected UUID packUUID; public AbstractPackManager(CraftEngine plugin, BiConsumer eventDispatcher) { this.plugin = plugin; @@ -519,22 +514,6 @@ public abstract class AbstractPackManager implements PackManager { this.plugin.logger().info("Finished generating resource pack in " + (end - start) + "ms"); this.eventDispatcher.accept(generatedPackPath, zipFile); - this.calculateHash(); - } - - private void calculateHash() { - Path zipFile = selfHostPackPath(); - if (Files.exists(zipFile)) { - try { - this.packHash = computeSHA1(zipFile); - this.packUUID = UUID.nameUUIDFromBytes(this.packHash.getBytes(StandardCharsets.UTF_8)); - } catch (IOException | NoSuchAlgorithmException e) { - this.plugin.logger().severe("Error calculating resource pack hash", e); - } - } else { - this.packHash = ""; - this.packUUID = UUID.nameUUIDFromBytes("EMPTY".getBytes(StandardCharsets.UTF_8)); - } } private void generateParticle(Path generatedPackPath) { @@ -1128,23 +1107,6 @@ public abstract class AbstractPackManager implements PackManager { } } - protected String computeSHA1(Path path) throws IOException, NoSuchAlgorithmException { - InputStream file = Files.newInputStream(path); - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - byte[] buffer = new byte[8192]; - int bytesRead; - while ((bytesRead = file.read(buffer)) != -1) { - digest.update(buffer, 0, bytesRead); - } - file.close(); - - StringBuilder hexString = new StringBuilder(40); - for (byte b : digest.digest()) { - hexString.append(String.format("%02x", b)); - } - return hexString.toString(); - } - private List>> mergeFolder(Collection sourceFolders, Path targetFolder) throws IOException { Map> conflictChecker = new HashMap<>(); for (Path sourceFolder : sourceFolders) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java index 13beaf412..709381e51 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.pack.host; +import java.nio.file.Path; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -8,4 +9,6 @@ public interface ResourcePackHost { CompletableFuture requestResourcePackDownloadLink(UUID player); ResourcePackDownloadData getResourcePackDownloadLink(UUID player); + + CompletableFuture upload(Path resourcePackPath); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index aabe7a017..5b8880e02 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.pack.host.impl; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import java.nio.file.Path; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -17,4 +18,9 @@ public class SelfHost implements ResourcePackHost { public ResourcePackDownloadData getResourcePackDownloadLink(UUID player) { return null; } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + return null; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 4722a0d2a..2c05ed049 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -1,5 +1,7 @@ package net.momirealms.craftengine.core.pack.host.impl; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; @@ -7,45 +9,74 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class SelfHostHttpServer { private static SelfHostHttpServer instance; + private Cache oneTimePackUrls = Caffeine.newBuilder() + .expireAfterAccess(1, TimeUnit.MINUTES) + .build(); private HttpServer server; - private String ip; - private int port; - private Path resourcePackPath; private final ConcurrentHashMap ipAccessMap = new ConcurrentHashMap<>(); private int rateLimit = 1; private long rateLimitInterval = 1000; + private String ip = "localhost"; + private int port = -1; + private Path resourcePackPath; + private String packHash; + private UUID packUUID; + + public String generateOneTimeUrl(UUID player) { + + } public String url() { return Config.hostProtocol() + "://" + ip + ":" + port + "/"; } - public void enable(String ip, int port, Path resourcePackPath) { - if (isAlive() && ip.equals(this.ip) && port == this.port && resourcePackPath.equals(this.resourcePackPath)) { + public void setResourcePackPath(Path resourcePackPath) { + this.resourcePackPath = resourcePackPath; + } + + private void calculateHash() { + if (Files.exists(this.resourcePackPath)) { + try { + this.packHash = computeSHA1(this.resourcePackPath); + this.packUUID = UUID.nameUUIDFromBytes(this.packHash.getBytes(StandardCharsets.UTF_8)); + } catch (IOException | NoSuchAlgorithmException e) { + CraftEngine.instance().logger().severe("Error calculating resource pack hash", e); + } + } else { + this.packHash = ""; + this.packUUID = UUID.nameUUIDFromBytes("EMPTY".getBytes(StandardCharsets.UTF_8)); + } + } + + public void updatePort(int port) { + if (port == this.port) { return; } if (server != null) { disable(); } - this.ip = ip; this.port = port; - this.resourcePackPath = resourcePackPath; - try { server = HttpServer.create(new InetSocketAddress("::", port), 0); server.createContext("/", new ResourcePackHandler()); server.setExecutor(Executors.newCachedThreadPool()); server.start(); - CraftEngine.instance().logger().info("HTTP resource pack server running on " + ip + ":" + port); + CraftEngine.instance().logger().info("HTTP resource pack server running on port: " + port); } catch (IOException e) { CraftEngine.instance().logger().warn("Failed to start HTTP server", e); } @@ -74,6 +105,27 @@ public class SelfHostHttpServer { this.rateLimitInterval = timeUnit.toMillis(rateLimitInterval); } + public void setIp(String ip) { + this.ip = ip; + } + + private String computeSHA1(Path path) throws IOException, NoSuchAlgorithmException { + InputStream file = Files.newInputStream(path); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = file.read(buffer)) != -1) { + digest.update(buffer, 0, bytesRead); + } + file.close(); + + StringBuilder hexString = new StringBuilder(40); + for (byte b : digest.digest()) { + hexString.append(String.format("%02x", b)); + } + return hexString.toString(); + } + private class ResourcePackHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index b85fcb74d..52120eaef 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -32,7 +32,10 @@ import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; public class Config { diff --git a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java b/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java index 99848d288..6cb356a90 100644 --- a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java +++ b/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java @@ -1,8 +1,5 @@ package net.momirealms.craftengine.mod; -import io.netty.buffer.Unpooled; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.ClassNode; @@ -11,7 +8,6 @@ import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import java.net.URISyntaxException; import java.net.URL; -import java.nio.Buffer; import java.nio.file.Path; import java.nio.file.Paths; import java.security.CodeSource; From d54c14f66afc101b7252652e88f66ece19a787e0 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 17 Apr 2025 02:45:34 +0800 Subject: [PATCH 07/62] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=E6=80=A7token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/AbstractPackManager.java | 1 - .../pack/host/ResourcePackDownloadData.java | 4 + .../core/pack/host/impl/SelfHost.java | 12 +- .../pack/host/impl/SelfHostHttpServer.java | 338 ++++++++++++------ .../pack/host/impl/SimpleExternalHost.java | 6 + 5 files changed, 239 insertions(+), 122 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index b223f2e11..d0fe4351f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -143,7 +143,6 @@ public abstract class AbstractPackManager implements PackManager { @Override public void load() { - this.calculateHash(); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java index 139b42814..9a39f1d70 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java @@ -3,4 +3,8 @@ package net.momirealms.craftengine.core.pack.host; import java.util.UUID; public record ResourcePackDownloadData(String url, UUID uuid, String sha1) { + + public static ResourcePackDownloadData of(String url, UUID uuid, String sha1) { + return new ResourcePackDownloadData(url, uuid, sha1); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index 5b8880e02..32f117c07 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -9,18 +9,24 @@ import java.util.concurrent.CompletableFuture; public class SelfHost implements ResourcePackHost { + public SelfHost(String ip, int port) { + SelfHostHttpServer.instance().setIp(ip); + SelfHostHttpServer.instance().updatePort(port); + } + @Override public CompletableFuture requestResourcePackDownloadLink(UUID player) { - return null; + return CompletableFuture.completedFuture(SelfHostHttpServer.instance().generateOneTimeUrl(player)); } @Override public ResourcePackDownloadData getResourcePackDownloadLink(UUID player) { - return null; + return SelfHostHttpServer.instance().getCachedOneTimeUrl(player); } @Override public CompletableFuture upload(Path resourcePackPath) { - return null; + SelfHostHttpServer.instance().setResourcePackPath(resourcePackPath); + return CompletableFuture.completedFuture(true); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 2c05ed049..4426f1e3f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -5,40 +5,82 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; +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.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; public class SelfHostHttpServer { private static SelfHostHttpServer instance; - private Cache oneTimePackUrls = Caffeine.newBuilder() + private final Cache oneTimePackUrls = Caffeine.newBuilder() + .maximumSize(256) .expireAfterAccess(1, TimeUnit.MINUTES) .build(); + private final Cache playerTokens = Caffeine.newBuilder() + .maximumSize(256) + .expireAfterAccess(1, TimeUnit.MINUTES) + .build(); + private final Cache ipAccessCache = Caffeine.newBuilder() + .maximumSize(256) + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + + private final ExecutorService threadPool = Executors.newFixedThreadPool(1); private HttpServer server; - private final ConcurrentHashMap ipAccessMap = new ConcurrentHashMap<>(); + + private final AtomicLong totalRequests = new AtomicLong(); + private final AtomicLong blockedRequests = new AtomicLong(); + private int rateLimit = 1; private long rateLimitInterval = 1000; private String ip = "localhost"; private int port = -1; - private Path resourcePackPath; + + private volatile byte[] resourcePackBytes; private String packHash; private UUID packUUID; - public String generateOneTimeUrl(UUID player) { + public void setIp(String ip) { + this.ip = ip; + } + @NotNull + public ResourcePackDownloadData generateOneTimeUrl(UUID player) { + String token = UUID.randomUUID().toString(); + this.oneTimePackUrls.put(token, true); + this.playerTokens.put(player, token); + return new ResourcePackDownloadData( + url() + "download?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8), + packUUID, + packHash + ); + } + + @Nullable + public ResourcePackDownloadData getCachedOneTimeUrl(UUID player) { + String token = this.playerTokens.getIfPresent(player); + if (token == null) return null; + return new ResourcePackDownloadData( + url() + "download?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8), + packUUID, + packHash + ); } public String url() { @@ -46,51 +88,200 @@ public class SelfHostHttpServer { } public void setResourcePackPath(Path resourcePackPath) { - this.resourcePackPath = resourcePackPath; + try { + if (Files.exists(resourcePackPath)) { + this.resourcePackBytes = Files.readAllBytes(resourcePackPath); + calculateHash(); + } else { + CraftEngine.instance().logger().warn("Resource pack file not found: " + resourcePackPath); + } + } catch (IOException e) { + CraftEngine.instance().logger().severe("Failed to load resource pack", e); + } } private void calculateHash() { - if (Files.exists(this.resourcePackPath)) { - try { - this.packHash = computeSHA1(this.resourcePackPath); - this.packUUID = UUID.nameUUIDFromBytes(this.packHash.getBytes(StandardCharsets.UTF_8)); - } catch (IOException | NoSuchAlgorithmException e) { - CraftEngine.instance().logger().severe("Error calculating resource pack hash", e); + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(resourcePackBytes); + byte[] hashBytes = digest.digest(); + + StringBuilder hexString = new StringBuilder(); + for (byte b : hashBytes) { + hexString.append(String.format("%02x", b)); } - } else { - this.packHash = ""; - this.packUUID = UUID.nameUUIDFromBytes("EMPTY".getBytes(StandardCharsets.UTF_8)); + this.packHash = hexString.toString(); + this.packUUID = UUID.nameUUIDFromBytes(packHash.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + CraftEngine.instance().logger().severe("SHA-1 algorithm not available", e); } } public void updatePort(int port) { - if (port == this.port) { - return; - } - if (server != null) { - disable(); - } + if (port == this.port) return; + if (server != null) disable(); this.port = port; try { server = HttpServer.create(new InetSocketAddress("::", port), 0); - server.createContext("/", new ResourcePackHandler()); - server.setExecutor(Executors.newCachedThreadPool()); + server.createContext("/download", new ResourcePackHandler()); + server.createContext("/metrics", this::handleMetrics); + server.setExecutor(threadPool); server.start(); - CraftEngine.instance().logger().info("HTTP resource pack server running on port: " + port); + CraftEngine.instance().logger().info("HTTP server started on port: " + port); } catch (IOException e) { CraftEngine.instance().logger().warn("Failed to start HTTP server", e); } } + private void handleMetrics(HttpExchange exchange) throws IOException { + String metrics = "# TYPE total_requests counter\n" + + "total_requests " + totalRequests.get() + "\n" + + "# TYPE blocked_requests counter\n" + + "blocked_requests " + blockedRequests.get(); + + exchange.getResponseHeaders().set("Content-Type", "text/plain"); + exchange.sendResponseHeaders(200, metrics.length()); + try (OutputStream os = exchange.getResponseBody()) { + os.write(metrics.getBytes(StandardCharsets.UTF_8)); + } + } + public void disable() { if (server != null) { server.stop(0); server = null; + threadPool.shutdownNow(); } } - public boolean isAlive() { - return server != null; + public void adjustRateLimit(int requestsPerSecond, int rateLimitInterval) { + this.rateLimit = requestsPerSecond; + this.rateLimitInterval = rateLimitInterval; + CraftEngine.instance().logger().info("Updated rate limit to " + requestsPerSecond + "/s"); + } + + private class ResourcePackHandler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) throws IOException { + totalRequests.incrementAndGet(); + + String clientIp = getClientIp(exchange); + + if (checkRateLimit(clientIp)) { + handleBlockedRequest(exchange, 429, "Rate limit exceeded"); + return; + } + + String token = parseToken(exchange); + if (!validateToken(token)) { + handleBlockedRequest(exchange, 403, "Invalid token"); + return; + } + + if (!validateClient(exchange)) { + handleBlockedRequest(exchange, 403, "Invalid client"); + return; + } + + if (resourcePackBytes == null) { + handleBlockedRequest(exchange, 404, "Resource pack missing"); + return; + } + + sendResourcePack(exchange); + } + + private String getClientIp(HttpExchange exchange) { + return exchange.getRemoteAddress().getAddress().getHostAddress(); + } + + private boolean checkRateLimit(String clientIp) { + IpAccessRecord record = ipAccessCache.getIfPresent(clientIp); + long now = System.currentTimeMillis(); + if (record == null) { + record = new IpAccessRecord(now, 1); + ipAccessCache.put(clientIp, record); + } else { + if (now - record.lastAccessTime > rateLimitInterval) { + record = new IpAccessRecord(now, 1); + ipAccessCache.put(clientIp, record); + } else { + record.accessCount++; + } + } + return record.accessCount > rateLimit; + } + + private String parseToken(HttpExchange exchange) { + Map params = parseQuery(exchange.getRequestURI().getQuery()); + return params.get("token"); + } + + private boolean validateToken(String token) { + if (token == null || token.length() != 36) return false; + + Boolean valid = oneTimePackUrls.getIfPresent(token); + if (valid != null) { + oneTimePackUrls.invalidate(token); + return true; + } + return false; + } + + private boolean validateClient(HttpExchange exchange) { + if (!Config.denyNonMinecraftRequest()) return true; + + String userAgent = exchange.getRequestHeaders().getFirst("User-Agent"); + return userAgent != null && userAgent.startsWith("Minecraft Java/"); + } + + private void sendResourcePack(HttpExchange exchange) throws IOException { + exchange.getResponseHeaders().set("Content-Type", "application/zip"); + exchange.getResponseHeaders().set("Content-Length", String.valueOf(resourcePackBytes.length)); + exchange.sendResponseHeaders(200, resourcePackBytes.length); + + try (OutputStream os = exchange.getResponseBody()) { + os.write(resourcePackBytes); + } catch (IOException e) { + CraftEngine.instance().logger().warn("Failed to send resource pack", e); + throw e; + } + } + + private void handleBlockedRequest(HttpExchange exchange, int code, String reason) throws IOException { + blockedRequests.incrementAndGet(); + CraftEngine.instance().debug(() -> + String.format("Blocked request [%s] %s: %s", + code, + exchange.getRemoteAddress(), + reason) + ); + exchange.sendResponseHeaders(code, -1); + exchange.close(); + } + + private Map parseQuery(String query) { + Map params = new HashMap<>(); + if (query == null) return params; + + for (String pair : query.split("&")) { + int idx = pair.indexOf("="); + String key = idx > 0 ? pair.substring(0, idx) : pair; + String value = idx > 0 ? pair.substring(idx + 1) : ""; + params.put(key, value); + } + return params; + } + } + + private static class IpAccessRecord { + final long lastAccessTime; + int accessCount; + + IpAccessRecord(long lastAccessTime, int accessCount) { + this.lastAccessTime = lastAccessTime; + this.accessCount = accessCount; + } } public static SelfHostHttpServer instance() { @@ -99,93 +290,4 @@ public class SelfHostHttpServer { } return instance; } - - public void setRateLimit(int rateLimit, long rateLimitInterval, TimeUnit timeUnit) { - this.rateLimit = rateLimit; - this.rateLimitInterval = timeUnit.toMillis(rateLimitInterval); - } - - public void setIp(String ip) { - this.ip = ip; - } - - private String computeSHA1(Path path) throws IOException, NoSuchAlgorithmException { - InputStream file = Files.newInputStream(path); - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - byte[] buffer = new byte[8192]; - int bytesRead; - while ((bytesRead = file.read(buffer)) != -1) { - digest.update(buffer, 0, bytesRead); - } - file.close(); - - StringBuilder hexString = new StringBuilder(40); - for (byte b : digest.digest()) { - hexString.append(String.format("%02x", b)); - } - return hexString.toString(); - } - - private class ResourcePackHandler implements HttpHandler { - @Override - public void handle(HttpExchange exchange) throws IOException { - if (Config.denyNonMinecraftRequest()) { - String userAgent = exchange.getRequestHeaders().getFirst("User-Agent"); - if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) { - CraftEngine.instance().debug(() -> "Blocked non-Minecraft Java client. User-Agent: " + userAgent); - sendError(exchange, 403); - return; - } - } - - String clientIp = exchange.getRemoteAddress().getAddress().getHostAddress(); - - IpAccessRecord record = ipAccessMap.compute(clientIp, (k, v) -> { - long currentTime = System.currentTimeMillis(); - if (v == null || currentTime - v.lastAccessTime > rateLimitInterval) { - return new IpAccessRecord(currentTime, 1); - } else { - v.accessCount++; - return v; - } - }); - - if (record.accessCount > rateLimit) { - CraftEngine.instance().debug(() -> "Rate limit exceeded for IP: " + clientIp); - sendError(exchange, 429); - return; - } - - if (!Files.exists(resourcePackPath)) { - CraftEngine.instance().logger().warn("ResourcePack not found: " + resourcePackPath); - sendError(exchange, 404); - return; - } - - exchange.getResponseHeaders().set("Content-Type", "application/zip"); - exchange.getResponseHeaders().set("Content-Length", String.valueOf(Files.size(resourcePackPath))); - exchange.sendResponseHeaders(200, Files.size(resourcePackPath)); - - try (OutputStream os = exchange.getResponseBody()) { - Files.copy(resourcePackPath, os); - } catch (IOException e) { - CraftEngine.instance().logger().warn("Failed to send pack", e); - } - } - - private void sendError(HttpExchange exchange, int code) throws IOException { - exchange.sendResponseHeaders(code, 0); - exchange.getResponseBody().close(); - } - } - - private static class IpAccessRecord { - long lastAccessTime; - int accessCount; - - IpAccessRecord(long lastAccessTime, int accessCount) { - this.lastAccessTime = lastAccessTime; - this.accessCount = accessCount; - } - } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java index a021836bd..4c736cfb1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.pack.host.impl; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import java.nio.file.Path; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -22,4 +23,9 @@ public class SimpleExternalHost implements ResourcePackHost { public ResourcePackDownloadData getResourcePackDownloadLink(UUID player) { return this.downloadData; } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + return CompletableFuture.completedFuture(true); + } } From bdb051276426da2a28868c8c3443a4a659f043ce Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 17 Apr 2025 03:05:28 +0800 Subject: [PATCH 08/62] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/pack/BukkitPackManager.java | 119 +++++++++++------- .../core/pack/host/ResourcePackHost.java | 2 - .../core/pack/host/impl/SelfHost.java | 5 - .../pack/host/impl/SelfHostHttpServer.java | 16 --- 4 files changed, 77 insertions(+), 65 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 896db1452..c11ff497d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -2,7 +2,9 @@ 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.util.ComponentUtils; import net.momirealms.craftengine.bukkit.util.EventUtils; +import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.pack.AbstractPackManager; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.VersionHelper; @@ -14,57 +16,90 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerResourcePackStatusEvent; +import java.util.Optional; +import java.util.UUID; + public class BukkitPackManager extends AbstractPackManager implements Listener { - private final BukkitCraftEngine plugin; + private final BukkitCraftEngine plugin; - public BukkitPackManager(BukkitCraftEngine plugin) { + public BukkitPackManager(BukkitCraftEngine plugin) { super(plugin, (rf, zp) -> { - AsyncResourcePackGenerateEvent endEvent = new AsyncResourcePackGenerateEvent(rf, zp); - EventUtils.fireAndForget(endEvent); - }); + AsyncResourcePackGenerateEvent endEvent = new AsyncResourcePackGenerateEvent(rf, zp); + EventUtils.fireAndForget(endEvent); + }); this.plugin = plugin; - } + } - @Override - public void delayedInit() { - super.delayedInit(); - Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap()); - } + @Override + public void delayedInit() { + super.delayedInit(); + Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap()); + } - @EventHandler(priority = EventPriority.LOW) - public void onPlayerJoin(PlayerJoinEvent event) { - // todo 1.20.1 资源包发送 - } + @EventHandler(priority = EventPriority.LOW) + public void onPlayerJoin(PlayerJoinEvent event) { + // todo 1.20.1 资源包发送 + } - @EventHandler(priority = EventPriority.LOW) - public void onResourcePackStatus(PlayerResourcePackStatusEvent event) { - // for 1.20.1 servers, not recommended to use - if (Config.sendPackOnJoin() && Config.kickOnDeclined() && !VersionHelper.isVersionNewerThan1_20_2()) { - if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED || event.getStatus() == PlayerResourcePackStatusEvent.Status.FAILED_DOWNLOAD) { - event.getPlayer().kick(); - } - } - } + @EventHandler(priority = EventPriority.LOW) + public void onResourcePackStatus(PlayerResourcePackStatusEvent event) { + // for 1.20.1 servers, not recommended to use + if (Config.sendPackOnJoin() && Config.kickOnDeclined() && !VersionHelper.isVersionNewerThan1_20_2()) { + if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED || event.getStatus() == PlayerResourcePackStatusEvent.Status.FAILED_DOWNLOAD) { + event.getPlayer().kick(); + } + } + } - @Override - public void load() { - super.load(); - } + @Override + public void load() { + if (Config.sendPackOnJoin()) { + this.modifyServerSettings(); + } + } - @Override - public void unload() { - super.unload(); - } + public void modifyServerSettings() { + try { + Object settings = Reflections.field$DedicatedServer$settings.get(Reflections.method$MinecraftServer$getServer.invoke(null)); + 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())); + } else { + info = Reflections.constructor$ServerResourcePackInfo.newInstance(new UUID(0, 0), "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt())); + } + Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.of(info)); + } catch (Exception e) { + this.plugin.logger().warn("Failed to update resource pack settings", e); + } + } - @Override - public void disable() { - super.disable(); - HandlerList.unregisterAll(this); - } + @Override + public void unload() { + super.unload(); + this.resetServerSettings(); + } - @Override - public void generateResourcePack() { - // generate pack - super.generateResourcePack(); - } + @Override + public void disable() { + super.disable(); + HandlerList.unregisterAll(this); + this.resetServerSettings(); + } + + public void resetServerSettings() { + try { + Object settings = Reflections.field$DedicatedServer$settings.get(Reflections.method$MinecraftServer$getServer.invoke(null)); + Object properties = Reflections.field$DedicatedServerSettings$properties.get(settings); + Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.empty()); + } catch (Exception e) { + this.plugin.logger().warn("Failed to reset resource pack settings", e); + } + } + + @Override + public void generateResourcePack() { + // generate pack + super.generateResourcePack(); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java index 709381e51..884e7d972 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java @@ -8,7 +8,5 @@ public interface ResourcePackHost { CompletableFuture requestResourcePackDownloadLink(UUID player); - ResourcePackDownloadData getResourcePackDownloadLink(UUID player); - CompletableFuture upload(Path resourcePackPath); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index 32f117c07..091c06185 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -19,11 +19,6 @@ public class SelfHost implements ResourcePackHost { return CompletableFuture.completedFuture(SelfHostHttpServer.instance().generateOneTimeUrl(player)); } - @Override - public ResourcePackDownloadData getResourcePackDownloadLink(UUID player) { - return SelfHostHttpServer.instance().getCachedOneTimeUrl(player); - } - @Override public CompletableFuture upload(Path resourcePackPath) { SelfHostHttpServer.instance().setResourcePackPath(resourcePackPath); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 4426f1e3f..6ca5466af 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -32,10 +32,6 @@ public class SelfHostHttpServer { .maximumSize(256) .expireAfterAccess(1, TimeUnit.MINUTES) .build(); - private final Cache playerTokens = Caffeine.newBuilder() - .maximumSize(256) - .expireAfterAccess(1, TimeUnit.MINUTES) - .build(); private final Cache ipAccessCache = Caffeine.newBuilder() .maximumSize(256) .expireAfterAccess(10, TimeUnit.MINUTES) @@ -64,18 +60,6 @@ public class SelfHostHttpServer { public ResourcePackDownloadData generateOneTimeUrl(UUID player) { String token = UUID.randomUUID().toString(); this.oneTimePackUrls.put(token, true); - this.playerTokens.put(player, token); - return new ResourcePackDownloadData( - url() + "download?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8), - packUUID, - packHash - ); - } - - @Nullable - public ResourcePackDownloadData getCachedOneTimeUrl(UUID player) { - String token = this.playerTokens.getIfPresent(player); - if (token == null) return null; return new ResourcePackDownloadData( url() + "download?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8), packUUID, From a6ac53b9d8c6fe797ddceef2146848332d0aee15 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Thu, 17 Apr 2025 05:26:04 +0800 Subject: [PATCH 09/62] =?UTF-8?q?feat(resource-pack):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E5=8C=85=E6=8E=A8=E9=80=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/pack/BukkitPackManager.java | 28 +++++++++- .../plugin/network/BukkitNetworkManager.java | 1 + .../plugin/network/PacketConsumers.java | 48 +++++++++++++++- .../plugin/user/BukkitServerPlayer.java | 10 ++++ .../craftengine/bukkit/util/Reflections.java | 56 ++++++++++++++++++- .../bukkit/util/ResourcePackUtils.java | 21 +++++++ .../core/pack/AbstractPackManager.java | 13 +++++ .../craftengine/core/pack/PackManager.java | 3 + .../core/pack/host/impl/SelfHost.java | 2 +- .../pack/host/impl/SelfHostHttpServer.java | 19 +++++-- .../pack/host/impl/SimpleExternalHost.java | 5 -- .../core/plugin/network/NetWorkUser.java | 4 ++ 12 files changed, 193 insertions(+), 17 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ResourcePackUtils.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index c11ff497d..dc14e0aee 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -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 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); + } + } + }); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index e3c8c0a48..f5f63ecd3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -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()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 4086b34fb..ae4da7c91 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -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 map, int registrySize) { mappings = new int[registrySize]; @@ -1158,7 +1164,6 @@ public class PacketConsumers { public static final TriConsumer 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 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 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); + } + }; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index d70c984b7..26e7fd093 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -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(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index c27611440..5a4187e4a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -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) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ResourcePackUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ResourcePackUtils.java new file mode 100644 index 000000000..4deb0f708 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ResourcePackUtils.java @@ -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())); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index d0fe4351f..f3510019b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -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 sectionParsers = new HashMap<>(); private final TreeMap> cachedConfigs = new TreeMap<>(); protected BiConsumer zipGenerator; + protected ResourcePackHost resourcePackHost; public AbstractPackManager(CraftEngine plugin, BiConsumer 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); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java index 54764ce6a..ae3552373 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java @@ -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(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index 091c06185..2b355e4c3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -16,7 +16,7 @@ public class SelfHost implements ResourcePackHost { @Override public CompletableFuture requestResourcePackDownloadLink(UUID player) { - return CompletableFuture.completedFuture(SelfHostHttpServer.instance().generateOneTimeUrl(player)); + return CompletableFuture.completedFuture(SelfHostHttpServer.instance().generateOneTimeUrl()); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 6ca5466af..5a78b3448 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -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(); + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java index 4c736cfb1..e53d56c82 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java @@ -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 upload(Path resourcePackPath) { return CompletableFuture.completedFuture(true); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 2c1ea4b94..91219eae0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -55,4 +55,8 @@ public interface NetWorkUser { @Nullable UUID currentResourcePackUUID(); + + boolean handleResourcePackPush(); + + void setHandleResourcePackPush(boolean handleFinishConfiguration); } From 49935322152e2096fef31c71a518ca29241a9548 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 17 Apr 2025 21:12:34 +0800 Subject: [PATCH 10/62] 0.0.49beta --- bukkit/loader/src/main/resources/config.yml | 27 ++--- .../bukkit/pack/BukkitPackManager.java | 82 +++++++++----- .../plugin/command/feature/ReloadCommand.java | 32 ++++-- .../plugin/network/BukkitNetworkManager.java | 2 +- .../plugin/network/PacketConsumers.java | 55 ++++----- .../plugin/user/BukkitServerPlayer.java | 45 +++----- .../bukkit/util/ResourcePackUtils.java | 14 +-- .../core/pack/AbstractPackManager.java | 15 ++- .../core/pack/host/ResourcePackHost.java | 5 +- .../pack/host/ResourcePackHostFactory.java | 8 ++ .../core/pack/host/ResourcePackHosts.java | 44 ++++++++ .../core/pack/host/impl/ExternalHost.java | 51 +++++++++ .../core/pack/host/impl/NoneHost.java | 34 ++++++ .../core/pack/host/impl/SelfHost.java | 61 ++++++++-- .../pack/host/impl/SelfHostHttpServer.java | 104 ++++++++++-------- .../pack/host/impl/SimpleExternalHost.java | 26 ----- .../craftengine/core/plugin/CraftEngine.java | 8 ++ .../craftengine/core/plugin/Plugin.java | 2 + .../core/plugin/config/Config.java | 67 ----------- .../core/plugin/network/NetWorkUser.java | 12 +- .../core/registry/BuiltInRegistries.java | 2 + .../craftengine/core/registry/Registries.java | 2 + .../core/util/CompletableFutures.java | 32 ++++++ gradle.properties | 6 +- 24 files changed, 433 insertions(+), 303 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHostFactory.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/NoneHost.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/CompletableFutures.java diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index 7883f4776..884d642cc 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -72,22 +72,17 @@ resource-pack: prompt: "To fully experience our server, please accept our custom resource pack." # If you are hosting the resource pack by yourself, replace `localhost` with your server ip otherwise it would only work on your local pc # If using BungeeCord or Velocity, consider using a proxy-side plugin to handle resource pack delivery. - mode: self-host # self-host/external-host/none - self-host: - ip: localhost - port: 8163 - protocol: http - deny-non-minecraft-request: true - # If the path begins with `./` or `../`, it is treated as a relative path to the plugin folder. - # Otherwise, it is considered an absolute path. - local-file-path: "./generated/resource_pack.zip" - rate-limit: - max-requests: 3 - reset-interval: 30 # seconds - external-host: - url: "" - sha1: "" - uuid: "" + # Read this page for more host types: https://mo-mi.gitbook.io/xiaomomi-plugins/craftengine/plugin-wiki/craftengine/resource-pack/host + host: + - type: "self_host" + ip: "localhost" + port: 8163 + protocol: "http" + deny-non-minecraft-request: true + local-file-path: "./generated/resource_pack.zip" + rate-limit: + max-requests: 3 + reset-interval: 20 duplicated-files-handler: - term: type: any_of diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index dc14e0aee..b437a7c6b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -2,13 +2,17 @@ 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.command.feature.ReloadCommand; 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.entity.player.Player; import net.momirealms.craftengine.core.pack.AbstractPackManager; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.impl.NoneHost; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Bukkit; @@ -19,11 +23,14 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerResourcePackStatusEvent; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; public class BukkitPackManager extends AbstractPackManager implements Listener { + public static final String FAKE_URL = "https://127.0.0.1:65536"; private final BukkitCraftEngine plugin; public BukkitPackManager(BukkitCraftEngine plugin) { @@ -42,7 +49,10 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { @EventHandler(priority = EventPriority.LOW) public void onPlayerJoin(PlayerJoinEvent event) { - // todo 1.20.1 资源包发送 + if (Config.sendPackOnJoin() && !VersionHelper.isVersionNewerThan1_20_2()) { + Player player = plugin.adapt(event.getPlayer()); + this.sendResourcePack(player); + } } @EventHandler(priority = EventPriority.LOW) @@ -57,9 +67,14 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { @Override public void load() { - super.load(); - if (Config.sendPackOnJoin()) { - this.modifyServerSettings(); + if (ReloadCommand.RELOAD_PACK_FLAG || CraftEngine.instance().isInitializing()) { + super.load(); + if (Config.sendPackOnJoin() && VersionHelper.isVersionNewerThan1_20_2() && !(resourcePackHost() instanceof NoneHost)) { + this.modifyServerSettings(); + } + } + if (CraftEngine.instance().isInitializing()) { + resourcePackHost().upload(this.resourcePackPath()); } } @@ -69,9 +84,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), "https://127.0.0.1:65536", "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt())); + info = Reflections.constructor$ServerResourcePackInfo.newInstance(new UUID(0, 0), FAKE_URL, "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt())); } else { - info = Reflections.constructor$ServerResourcePackInfo.newInstance("https://127.0.0.1:65536", "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt())); + info = Reflections.constructor$ServerResourcePackInfo.newInstance(FAKE_URL, "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt())); } Reflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.of(info)); } catch (Exception e) { @@ -82,7 +97,11 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { @Override public void unload() { super.unload(); - this.resetServerSettings(); + if (ReloadCommand.RELOAD_PACK_FLAG) { + if (VersionHelper.isVersionNewerThan1_20_2()) { + this.resetServerSettings(); + } + } } @Override @@ -102,26 +121,39 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { } } - @Override - public void generateResourcePack() { - // generate pack - super.generateResourcePack(); + @EventHandler(priority = EventPriority.MONITOR) + public void onAsyncResourcePackGenerate(AsyncResourcePackGenerateEvent event) { + resourcePackHost().upload(event.zipFilePath()).whenComplete((d, e) -> { + if (e != null) { + CraftEngine.instance().logger().warn("Failed to upload resource pack", e); + return; + } + if (!Config.sendPackOnReload()) return; + for (BukkitServerPlayer player : this.plugin.networkManager().onlineUsers()) { + sendResourcePack(player); + } + }); } - @EventHandler - public void onAsyncResourcePackGenerate(AsyncResourcePackGenerateEvent event) { - Bukkit.getOnlinePlayers().forEach(p -> { - BukkitServerPlayer user = this.plugin.adapt(p); - CompletableFuture 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); + private void sendResourcePack(Player player) { + CompletableFuture> future = resourcePackHost().requestResourcePackDownloadLink(player.uuid()); + future.thenAccept(dataList -> { + if (player.isOnline()) { + player.unloadCurrentResourcePack(); + if (dataList.isEmpty()) { + return; + } + if (dataList.size() == 1) { + ResourcePackDownloadData data = dataList.get(0); + player.sendPacket(ResourcePackUtils.createPacket(data.uuid(), data.url(), data.sha1()), true); + player.addResourcePackUUID(data.uuid()); + } else { + List packets = new ArrayList<>(); + for (ResourcePackDownloadData data : dataList) { + packets.add(ResourcePackUtils.createPacket(data.uuid(), data.url(), data.sha1())); + player.addResourcePackUUID(data.uuid()); + } + player.sendPackets(packets, true); } } }); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ReloadCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ReloadCommand.java index 8979f67ae..07f41414b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ReloadCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ReloadCommand.java @@ -35,7 +35,6 @@ public class ReloadCommand extends BukkitCommandFeature { } if (argument == ReloadArgument.CONFIG) { try { - RELOAD_PACK_FLAG = true; plugin().reloadPlugin(plugin().scheduler().async(), r -> plugin().scheduler().sync().run(r), false).thenAccept(reloadResult -> { handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_SUCCESS, Component.text(reloadResult.asyncTime() + reloadResult.syncTime()), @@ -49,19 +48,20 @@ public class ReloadCommand extends BukkitCommandFeature { } } else if (argument == ReloadArgument.RECIPE) { try { - RELOAD_PACK_FLAG = true; plugin().reloadPlugin(plugin().scheduler().async(), r -> plugin().scheduler().sync().run(r), true).thenAccept(reloadResult -> { handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_SUCCESS, Component.text(reloadResult.asyncTime() + reloadResult.syncTime()), Component.text(reloadResult.asyncTime()), Component.text(reloadResult.syncTime()) ); + }); } catch (Exception e) { handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_FAILURE); plugin().logger().warn("Failed to reload config", e); } } else if (argument == ReloadArgument.PACK) { + RELOAD_PACK_FLAG = true; plugin().scheduler().executeAsync(() -> { try { long time1 = System.currentTimeMillis(); @@ -69,24 +69,32 @@ public class ReloadCommand extends BukkitCommandFeature { long time2 = System.currentTimeMillis(); long packTime = time2 - time1; handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_SUCCESS, Component.text(packTime)); + } catch (Exception e) { handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_FAILURE); plugin().logger().warn("Failed to generate resource pack", e); + } finally { + RELOAD_PACK_FLAG = false; } }); } else if (argument == ReloadArgument.ALL) { + RELOAD_PACK_FLAG = true; try { plugin().reloadPlugin(plugin().scheduler().async(), r -> plugin().scheduler().sync().run(r), true).thenAcceptAsync(reloadResult -> { - long time1 = System.currentTimeMillis(); - plugin().packManager().generateResourcePack(); - long time2 = System.currentTimeMillis(); - long packTime = time2 - time1; - handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_SUCCESS, - Component.text(reloadResult.asyncTime() + reloadResult.syncTime() + packTime), - Component.text(reloadResult.asyncTime()), - Component.text(reloadResult.syncTime()), - Component.text(packTime) - ); + try { + long time1 = System.currentTimeMillis(); + plugin().packManager().generateResourcePack(); + long time2 = System.currentTimeMillis(); + long packTime = time2 - time1; + handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_SUCCESS, + Component.text(reloadResult.asyncTime() + reloadResult.syncTime() + packTime), + Component.text(reloadResult.asyncTime()), + Component.text(reloadResult.syncTime()), + Component.text(packTime) + ); + } finally { + RELOAD_PACK_FLAG = false; + } }, plugin().scheduler().async()); } catch (Exception e) { handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_FAILURE); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index f5f63ecd3..afe8aecd1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -288,7 +288,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes return hasModelEngine; } - public void receivePacket(@NotNull NetWorkUser player, Object packet) { + public void simulatePacket(@NotNull NetWorkUser player, Object packet) { Channel channel = player.nettyChannel(); if (channel.isOpen()) { List handlerNames = channel.pipeline().names(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index ae4da7c91..26cf532a4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -13,6 +13,7 @@ import net.momirealms.craftengine.bukkit.compatibility.modelengine.ModelEngineUt import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.pack.BukkitPackManager; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; @@ -28,7 +29,6 @@ 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; @@ -47,8 +47,6 @@ 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 { @@ -2105,39 +2103,26 @@ public class PacketConsumers { public static final TriConsumer RESOURCE_PACK_PUSH = (user, event, packet) -> { try { if (!VersionHelper.isVersionNewerThan1_20_2()) return; - if (user.handleResourcePackPush()) return; + // we should only handle fake urls + String url = FastNMS.INSTANCE.field$ClientboundResourcePackPushPacket$url(packet); + if (!url.equals(BukkitPackManager.FAKE_URL)) { + 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 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); + UUID packUUID = FastNMS.INSTANCE.field$ClientboundResourcePackPushPacket$uuid(packet); + ResourcePackHost host = CraftEngine.instance().packManager().resourcePackHost(); + host.requestResourcePackDownloadLink(user.uuid()).thenAccept(dataList -> { + if (dataList.isEmpty()) { + user.simulatePacket(FastNMS.INSTANCE.constructor$ServerboundResourcePackPacket$SUCCESSFULLY_LOADED(packUUID)); + return; + } + for (ResourcePackDownloadData data : dataList) { + Object newPacket = ResourcePackUtils.createPacket(data.uuid(), data.url(), data.sha1()); + user.nettyChannel().writeAndFlush(newPacket); + user.addResourcePackUUID(data.uuid()); + } + }); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundResourcePackPushPacket", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 26e7fd093..3881c558c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -34,10 +34,7 @@ import org.jetbrains.annotations.Nullable; import java.lang.ref.Reference; import java.lang.ref.WeakReference; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class BukkitServerPlayer extends Player { @@ -48,7 +45,7 @@ public class BukkitServerPlayer extends Player { private UUID uuid; private ConnectionState decoderState; private ConnectionState encoderState; - private UUID resourcePackUUID; + private final Set resourcePackUUID = Collections.synchronizedSet(new HashSet<>()); // some references private Reference playerRef; private Reference serverPlayerRef; @@ -75,8 +72,6 @@ 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 @@ -273,8 +268,8 @@ public class BukkitServerPlayer extends Player { } @Override - public void receivePacket(Object packet) { - this.plugin.networkManager().receivePacket(this, packet); + public void simulatePacket(Object packet) { + this.plugin.networkManager().simulatePacket(this, packet); } @Override @@ -747,21 +742,10 @@ public class BukkitServerPlayer extends Player { } @Override - public void setCurrentResourcePackUUID(UUID uuid) { - this.resourcePackUUID = uuid; - } - - @Override - public @Nullable UUID currentResourcePackUUID() { - return this.resourcePackUUID; - } - - public boolean handleResourcePackPush() { - return this.handleResourcePackPush; - } - - public void setHandleResourcePackPush(boolean handleResourcePackPush) { - this.handleResourcePackPush = handleResourcePackPush; + public void addResourcePackUUID(UUID uuid) { + if (VersionHelper.isVersionNewerThan1_20_3()) { + this.resourcePackUUID.add(uuid); + } } @Override @@ -772,13 +756,14 @@ public class BukkitServerPlayer extends Player { @Override public void unloadCurrentResourcePack() { - if (decoderState() == ConnectionState.PLAY && this.resourcePackUUID != null && VersionHelper.isVersionNewerThan1_20_3()) { - try { - sendPacket(Reflections.constructor$ClientboundResourcePackPopPacket.newInstance(Optional.of(this.resourcePackUUID)), true); - this.resourcePackUUID = null; - } catch (ReflectiveOperationException e) { - this.plugin.logger().warn("Failed to unload resource pack for player " + name()); + if (!VersionHelper.isVersionNewerThan1_20_3()) { + return; + } + if (decoderState() == ConnectionState.PLAY && !this.resourcePackUUID.isEmpty()) { + for (UUID u : this.resourcePackUUID) { + sendPacket(FastNMS.INSTANCE.constructor$ClientboundResourcePackPopPacket(u), true); } + this.resourcePackUUID.clear(); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ResourcePackUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ResourcePackUtils.java index 4deb0f708..8d15638b3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ResourcePackUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ResourcePackUtils.java @@ -1,21 +1,13 @@ package net.momirealms.craftengine.bukkit.util; +import net.momirealms.craftengine.bukkit.nms.FastNMS; 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())); - } + public static Object createPacket(UUID uuid, String url, String hash) { + return FastNMS.INSTANCE.constructor$ClientboundResourcePackPushPacket(uuid, url, hash, Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt())); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index f3510019b..7757af7d9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -8,7 +8,8 @@ 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.host.ResourcePackHosts; +import net.momirealms.craftengine.core.pack.host.impl.NoneHost; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; import net.momirealms.craftengine.core.pack.model.ItemModel; import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; @@ -146,9 +147,11 @@ 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()); + List> list = Config.instance().settings().getMapList("resource-pack.send.host"); + if (list == null || list.isEmpty()) { + this.resourcePackHost = NoneHost.INSTANCE; + } else { + this.resourcePackHost = ResourcePackHosts.fromMap(MiscUtils.castToMap(list.get(0), false)); } } @@ -219,10 +222,6 @@ public abstract class AbstractPackManager implements PackManager { return true; } - public Path selfHostPackPath() { - return Config.hostResourcePackPath().startsWith(".") ? plugin.dataFolderPath().resolve(Config.hostResourcePackPath()) : Path.of(Config.hostResourcePackPath()); - } - private void loadPacks() { Path resourcesFolder = this.plugin.dataFolderPath().resolve("resources"); try { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java index 884e7d972..1da990f1d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java @@ -1,12 +1,13 @@ package net.momirealms.craftengine.core.pack.host; import java.nio.file.Path; +import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; public interface ResourcePackHost { - CompletableFuture requestResourcePackDownloadLink(UUID player); + CompletableFuture> requestResourcePackDownloadLink(UUID player); - CompletableFuture upload(Path resourcePackPath); + CompletableFuture upload(Path resourcePackPath); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHostFactory.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHostFactory.java new file mode 100644 index 000000000..21c8bbf27 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHostFactory.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.pack.host; + +import java.util.Map; + +public interface ResourcePackHostFactory { + + ResourcePackHost create(Map arguments); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java new file mode 100644 index 000000000..8bb56954d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -0,0 +1,44 @@ +package net.momirealms.craftengine.core.pack.host; + +import net.momirealms.craftengine.core.pack.host.impl.ExternalHost; +import net.momirealms.craftengine.core.pack.host.impl.NoneHost; +import net.momirealms.craftengine.core.pack.host.impl.SelfHost; +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.Key; +import net.momirealms.craftengine.core.util.ResourceKey; + +import java.util.Map; + +public class ResourcePackHosts { + public static final Key NONE = Key.of("craftengine:none"); + public static final Key SELF_HOST = Key.of("craftengine:self_host"); + public static final Key EXTERNAL_HOST = Key.of("craftengine:external_host"); + + static { + register(NONE, NoneHost.FACTORY); + register(SELF_HOST, SelfHost.FACTORY); + register(EXTERNAL_HOST, ExternalHost.FACTORY); + } + + public static void register(Key key, ResourcePackHostFactory factory) { + Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.RESOURCE_PACK_HOST_FACTORY) + .registerForHolder(new ResourceKey<>(Registries.RESOURCE_PACK_HOST_FACTORY.location(), key)); + holder.bindValue(factory); + } + + public static ResourcePackHost fromMap(Map map) { + String type = (String) map.get("type"); + if (type == null) { + throw new NullPointerException("host type cannot be null"); + } + Key key = Key.withDefaultNamespace(type, "craftengine"); + ResourcePackHostFactory factory = BuiltInRegistries.RESOURCE_PACK_HOST_FACTORY.getValue(key); + if (factory == null) { + throw new IllegalArgumentException("Unknown resource pack host type: " + type); + } + return factory.create(map); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java new file mode 100644 index 000000000..990f6e9fe --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class ExternalHost implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + private final ResourcePackDownloadData downloadData; + + public ExternalHost(ResourcePackDownloadData downloadData) { + this.downloadData = downloadData; + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + return CompletableFuture.completedFuture(List.of(this.downloadData)); + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + return CompletableFuture.completedFuture(null); + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + String url = (String) arguments.get("url"); + if (url == null || url.isEmpty()) { + throw new IllegalArgumentException("'url' cannot be empty for external host"); + } + String uuid = (String) arguments.get("uuid"); + if (uuid == null || uuid.isEmpty()) { + uuid = UUID.nameUUIDFromBytes(url.getBytes()).toString(); + } + UUID hostUUID = UUID.fromString(uuid); + String sha1 = (String) arguments.get("sha1"); + if (sha1 == null) { + sha1 = ""; + } + return new ExternalHost(new ResourcePackDownloadData(url, hostUUID, sha1)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/NoneHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/NoneHost.java new file mode 100644 index 000000000..6a8d4325b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/NoneHost.java @@ -0,0 +1,34 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class NoneHost implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + public static final NoneHost INSTANCE = new NoneHost(); + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + return CompletableFuture.completedFuture(List.of()); + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + return CompletableFuture.completedFuture(null); + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + return INSTANCE; + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index 2b355e4c3..45c4125dd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -2,26 +2,69 @@ package net.momirealms.craftengine.core.pack.host.impl; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.MiscUtils; import java.nio.file.Path; +import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; public class SelfHost implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + private static final SelfHost INSTANCE = new SelfHost(); - public SelfHost(String ip, int port) { - SelfHostHttpServer.instance().setIp(ip); - SelfHostHttpServer.instance().updatePort(port); + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + ResourcePackDownloadData data = SelfHostHttpServer.instance().generateOneTimeUrl(); + if (data == null) return CompletableFuture.completedFuture(List.of()); + return CompletableFuture.completedFuture(List.of(data)); } @Override - public CompletableFuture requestResourcePackDownloadLink(UUID player) { - return CompletableFuture.completedFuture(SelfHostHttpServer.instance().generateOneTimeUrl()); + public CompletableFuture upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + CraftEngine.instance().scheduler().executeAsync(() -> { + try { + SelfHostHttpServer.instance().readResourcePack(); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + return future; } - @Override - public CompletableFuture upload(Path resourcePackPath) { - SelfHostHttpServer.instance().setResourcePackPath(resourcePackPath); - return CompletableFuture.completedFuture(true); + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + SelfHostHttpServer selfHostHttpServer = SelfHostHttpServer.instance(); + String ip = (String) arguments.get("ip"); + if (ip == null) { + throw new IllegalArgumentException("'ip' argument missing for self host"); + } + int port = (int) arguments.get("port"); + if (port < 0 || port > 65535) { + throw new IllegalArgumentException("Illegal port: '" + port + "' for self host"); + } + String protocol = (String) arguments.getOrDefault("protocol", "http"); + boolean denyNonMinecraftRequest = (boolean) arguments.getOrDefault("deny-non-minecraft-request", true); + String localFilePath = (String) arguments.get("local-file-path"); + if (localFilePath == null) { + throw new IllegalArgumentException("'local-file-path' argument missing for self host"); + } + Map rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true); + int maxRequests = 5; + int resetInterval = 20_000; + if (rateMap != null) { + maxRequests = (int) rateMap.getOrDefault("max-requests", 5); + resetInterval = (int) rateMap.getOrDefault("reset-interval", 20) * 1000; + } + selfHostHttpServer.updateProperties(ip, port, denyNonMinecraftRequest, protocol, localFilePath, maxRequests, resetInterval); + return INSTANCE; + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 5a78b3448..11c3aacb0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -7,8 +7,8 @@ import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; 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; @@ -48,17 +48,59 @@ public class SelfHostHttpServer { private long rateLimitInterval = 1000; private String ip = "localhost"; private int port = -1; + private String protocol = "http"; + private boolean denyNonMinecraft = true; private volatile byte[] resourcePackBytes; private String packHash; private UUID packUUID; + private Path localFilePath = null; - public void setIp(String ip) { + public void updateProperties(String ip, + int port, + boolean denyNonMinecraft, + String protocol, + String localFile, + int maxRequests, + int resetInternal) { this.ip = ip; + this.port = port; + this.localFilePath = selfHostPackPath(localFile); + this.denyNonMinecraft = denyNonMinecraft; + this.protocol = protocol; + this.rateLimit = maxRequests; + this.rateLimitInterval = resetInternal; + if (port <= 0 || port > 65535) { + throw new IllegalArgumentException("Invalid port number: " + port); + } + if (port == this.port && server != null) return; + if (server != null) disable(); + this.port = port; + try { + threadPool = Executors.newFixedThreadPool(1); + server = HttpServer.create(new InetSocketAddress("::", port), 0); + server.createContext("/download", new ResourcePackHandler()); + server.createContext("/metrics", this::handleMetrics); + server.setExecutor(threadPool); + server.start(); + CraftEngine.instance().logger().info("HTTP server started on port: " + port); + } catch (IOException e) { + CraftEngine.instance().logger().warn("Failed to start HTTP server", e); + } } - @NotNull + public static SelfHostHttpServer instance() { + if (instance == null) { + instance = new SelfHostHttpServer(); + } + return instance; + } + + @Nullable public ResourcePackDownloadData generateOneTimeUrl() { + if (this.resourcePackBytes == null) { + return null; + } String token = UUID.randomUUID().toString(); this.oneTimePackUrls.put(token, true); return new ResourcePackDownloadData( @@ -68,17 +110,21 @@ public class SelfHostHttpServer { ); } - public String url() { - return Config.hostProtocol() + "://" + ip + ":" + port + "/"; + private Path selfHostPackPath(String path) { + return path.startsWith(".") ? CraftEngine.instance().dataFolderPath().resolve(path) : Path.of(path); } - public void setResourcePackPath(Path resourcePackPath) { + public String url() { + return protocol + "://" + ip + ":" + port + "/"; + } + + public void readResourcePack() { try { - if (Files.exists(resourcePackPath)) { - this.resourcePackBytes = Files.readAllBytes(resourcePackPath); + if (Files.exists(this.localFilePath)) { + this.resourcePackBytes = Files.readAllBytes(this.localFilePath); calculateHash(); } else { - CraftEngine.instance().logger().warn("Resource pack file not found: " + resourcePackPath); + this.resourcePackBytes = null; } } catch (IOException e) { CraftEngine.instance().logger().severe("Failed to load resource pack", e); @@ -102,26 +148,6 @@ 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 { - threadPool = Executors.newFixedThreadPool(1); - server = HttpServer.create(new InetSocketAddress(port), 0); - server.createContext("/download", new ResourcePackHandler()); - server.createContext("/metrics", this::handleMetrics); - server.setExecutor(threadPool); - server.start(); - CraftEngine.instance().logger().info("HTTP server started on port: " + port); - } catch (IOException e) { - CraftEngine.instance().logger().warn("Failed to start HTTP server", e); - } - } - private void handleMetrics(HttpExchange exchange) throws IOException { String metrics = "# TYPE total_requests counter\n" + "total_requests " + totalRequests.get() + "\n" @@ -145,40 +171,29 @@ public class SelfHostHttpServer { } } - public void adjustRateLimit(int requestsPerSecond, int rateLimitInterval) { - this.rateLimit = requestsPerSecond; - this.rateLimitInterval = rateLimitInterval; - CraftEngine.instance().logger().info("Updated rate limit to " + requestsPerSecond + "/s"); - } - private class ResourcePackHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { totalRequests.incrementAndGet(); String clientIp = getClientIp(exchange); - if (checkRateLimit(clientIp)) { handleBlockedRequest(exchange, 429, "Rate limit exceeded"); return; } - String token = parseToken(exchange); if (!validateToken(token)) { handleBlockedRequest(exchange, 403, "Invalid token"); return; } - if (!validateClient(exchange)) { handleBlockedRequest(exchange, 403, "Invalid client"); return; } - if (resourcePackBytes == null) { handleBlockedRequest(exchange, 404, "Resource pack missing"); return; } - sendResourcePack(exchange); } @@ -220,7 +235,7 @@ public class SelfHostHttpServer { } private boolean validateClient(HttpExchange exchange) { - if (!Config.denyNonMinecraftRequest()) return true; + if (!denyNonMinecraft) return true; String userAgent = exchange.getRequestHeaders().getFirst("User-Agent"); return userAgent != null && userAgent.startsWith("Minecraft Java/"); @@ -274,11 +289,4 @@ public class SelfHostHttpServer { this.accessCount = accessCount; } } - - public static SelfHostHttpServer instance() { - if (instance == null) { - instance = new SelfHostHttpServer(); - } - return instance; - } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java deleted file mode 100644 index e53d56c82..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SimpleExternalHost.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.momirealms.craftengine.core.pack.host.impl; - -import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; - -import java.nio.file.Path; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -public class SimpleExternalHost implements ResourcePackHost { - private final ResourcePackDownloadData downloadData; - - public SimpleExternalHost(ResourcePackDownloadData downloadData) { - this.downloadData = downloadData; - } - - @Override - public CompletableFuture requestResourcePackDownloadLink(UUID player) { - return CompletableFuture.completedFuture(this.downloadData); - } - - @Override - public CompletableFuture upload(Path resourcePackPath) { - return CompletableFuture.completedFuture(true); - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 8f20802bf..5c970821c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -69,6 +69,7 @@ public abstract class CraftEngine implements Plugin { private final Consumer reloadEventDispatcher; private boolean isReloading; + private boolean isInitializing; private String buildByBit = "%%__BUILTBYBIT__%%"; private String polymart = "%%__POLYMART__%%"; @@ -191,6 +192,7 @@ public abstract class CraftEngine implements Plugin { } public void onPluginEnable() { + this.isInitializing = true; this.networkManager.init(); this.templateManager = new TemplateManagerImpl(); this.itemBrowserManager = new ItemBrowserManagerImpl(this); @@ -219,6 +221,7 @@ public abstract class CraftEngine implements Plugin { this.furnitureManager.delayedInit(); // set up some platform extra tasks this.platformDelayedEnable(); + this.isInitializing = false; }); } @@ -324,6 +327,11 @@ public abstract class CraftEngine implements Plugin { return isReloading; } + @Override + public boolean isInitializing() { + return isInitializing; + } + public abstract boolean hasPlaceholderAPI(); @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java index b7022151b..d9b766a7f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java @@ -41,6 +41,8 @@ public interface Plugin { boolean isReloading(); + boolean isInitializing(); + DependencyManager dependencyManager(); SchedulerAdapter scheduler(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 52120eaef..cd862445c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -29,13 +29,11 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.stream.Collectors; public class Config { @@ -86,18 +84,6 @@ public class Config { protected boolean resource_pack$send$send_on_reload; protected Component resource_pack$send$prompt; - protected int resource_pack$send$self_host$port; - protected String resource_pack$send$self_host$ip; - protected String resource_pack$send$self_host$protocol; - protected boolean resource_pack$send$self_host$deny_non_minecraft_request; - protected int resource_pack$send$self_host$rate_limit$max_requests; - protected long resource_pack$send$self_host$rate_limit$reset_interval; - protected String resource_pack$self_host$local_file_path; - - protected String resource_pack$external_host$url; - protected String resource_pack$external_host$sha1; - protected UUID resource_pack$external_host$uuid; - protected int performance$max_block_chain_update_limit; protected int performance$max_emojis_per_parse; @@ -217,22 +203,10 @@ public class Config { resource_pack$supported_version$max = getVersion(config.get("resource-pack.supported-version.max", "LATEST").toString()); resource_pack$merge_external_folders = config.getStringList("resource-pack.merge-external-folders"); //resource_pack$send$mode = HostMode.valueOf(config.getString("resource-pack.send.mode", "self-host").replace("-", "_").toUpperCase(Locale.ENGLISH)); - resource_pack$send$self_host$port = config.getInt("resource-pack.send.self-host.port", 8163); - resource_pack$send$self_host$ip = config.getString("resource-pack.send.self-host.ip", "localhost"); - resource_pack$self_host$local_file_path = config.getString("resource-pack.send.self-host.local-file-path", "./generated/resource_pack.zip"); - resource_pack$send$self_host$protocol = config.getString("resource-pack.send.self-host.protocol", "http"); resource_pack$send$send_on_join = config.getBoolean("resource-pack.send.send-on-join", true); resource_pack$send$send_on_reload = config.getBoolean("resource-pack.send.send-on-reload", true); resource_pack$send$kick_if_declined = config.getBoolean("resource-pack.send.kick-if-declined", true); - resource_pack$external_host$url = config.getString("resource-pack.send.external-host.url", ""); - resource_pack$external_host$sha1 = config.getString("resource-pack.send.external-host.sha1", ""); - String packUUIDStr = config.getString("resource-pack.send.external-host.uuid", ""); - resource_pack$external_host$uuid = packUUIDStr.isEmpty() ? UUID.nameUUIDFromBytes(resource_pack$external_host$url.getBytes(StandardCharsets.UTF_8)) : UUID.fromString(packUUIDStr); resource_pack$send$prompt = AdventureHelper.miniMessage().deserialize(config.getString("resource-pack.send.prompt", "To fully experience our server, please accept our custom resource pack.")); - resource_pack$send$self_host$rate_limit$reset_interval = config.getLong("resource-pack.send.self-host.rate-limit.reset-interval", 30L); - resource_pack$send$self_host$rate_limit$max_requests = config.getInt("resource-pack.send.self-host.rate-limit.max-requests", 3); - resource_pack$send$self_host$deny_non_minecraft_request = config.getBoolean("resource-pack.send.deny-non-minecraft-request", true); - resource_pack$protection$crash_tools$method_1 = config.getBoolean("resource-pack.protection.crash-tools.method-1", false); resource_pack$protection$crash_tools$method_2 = config.getBoolean("resource-pack.protection.crash-tools.method-2", false); resource_pack$protection$crash_tools$method_3 = config.getBoolean("resource-pack.protection.crash-tools.method-3", false); @@ -241,7 +215,6 @@ public class Config { resource_pack$protection$obfuscation$fake_directory = config.getBoolean("resource-pack.protection.obfuscation.fake-directory", false); resource_pack$protection$obfuscation$escape_unicode = config.getBoolean("resource-pack.protection.obfuscation.escape-unicode", false); resource_pack$protection$obfuscation$break_json = config.getBoolean("resource-pack.protection.obfuscation.break-json", false); - resource_pack$protection$obfuscation$resource_location$enable = config.getBoolean("resource-pack.protection.obfuscation.resource-location.enable", false); resource_pack$protection$obfuscation$resource_location$random_namespace$amount = config.getInt("resource-pack.protection.obfuscation.resource-location.random-namespace.amount", 32); resource_pack$protection$obfuscation$resource_location$random_namespace$length = config.getInt("resource-pack.protection.obfuscation.resource-location.random-namespace.length", 8); @@ -444,10 +417,6 @@ public class Config { return instance.chunk_system$restore_vanilla_blocks_on_chunk_unload && instance.chunk_system$restore_custom_blocks_on_chunk_load; } - public static boolean denyNonMinecraftRequest() { - return instance.resource_pack$send$self_host$deny_non_minecraft_request; - } - public static boolean restoreCustomBlocks() { return instance.chunk_system$restore_custom_blocks_on_chunk_load; } @@ -460,14 +429,6 @@ public class Config { return instance.resource_pack$merge_external_folders; } - public static String hostIP() { - return instance.resource_pack$send$self_host$ip; - } - - public static int hostPort() { - return instance.resource_pack$send$self_host$port; - } - public static boolean kickOnDeclined() { return instance.resource_pack$send$kick_if_declined; } @@ -476,22 +437,6 @@ public class Config { return instance.resource_pack$send$prompt; } - public static String hostProtocol() { - return instance.resource_pack$send$self_host$protocol; - } - - public static String externalPackUrl() { - return instance.resource_pack$external_host$url; - } - - public static String externalPackSha1() { - return instance.resource_pack$external_host$sha1; - } - - public static UUID externalPackUUID() { - return instance.resource_pack$external_host$uuid; - } - public static boolean sendPackOnJoin() { return instance.resource_pack$send$send_on_join; } @@ -500,18 +445,6 @@ public class Config { return instance.resource_pack$send$send_on_reload; } - public static int requestRate() { - return instance.resource_pack$send$self_host$rate_limit$max_requests; - } - - public static long requestInterval() { - return instance.resource_pack$send$self_host$rate_limit$reset_interval; - } - - public static String hostResourcePackPath() { - return instance.resource_pack$self_host$local_file_path; - } - public static List resolutions() { return instance.resource_pack$duplicated_files_handler; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 91219eae0..f2a8b8d2d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -4,7 +4,6 @@ import io.netty.channel.Channel; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; @@ -27,7 +26,7 @@ public interface NetWorkUser { void sendPacket(Object packet, boolean immediately); - void receivePacket(Object packet); + void simulatePacket(Object packet); @ApiStatus.Internal ConnectionState decoderState(); @@ -51,12 +50,5 @@ public interface NetWorkUser { void setClientModState(boolean enable); - void setCurrentResourcePackUUID(UUID uuid); - - @Nullable - UUID currentResourcePackUUID(); - - boolean handleResourcePackPush(); - - void setHandleResourcePackPush(boolean handleFinishConfiguration); + void addResourcePackUUID(UUID uuid); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java index 8d2ed047b..59d7b9711 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.loot.function.LootFunctionFactory; import net.momirealms.craftengine.core.loot.number.NumberProviderFactory; import net.momirealms.craftengine.core.pack.conflict.matcher.PathMatcherFactory; import net.momirealms.craftengine.core.pack.conflict.resolution.ResolutionFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.pack.model.ItemModelFactory; import net.momirealms.craftengine.core.pack.model.condition.ConditionPropertyFactory; import net.momirealms.craftengine.core.pack.model.rangedisptach.RangeDispatchPropertyFactory; @@ -47,6 +48,7 @@ public class BuiltInRegistries { public static final Registry RESOLUTION_FACTORY = createRegistry(Registries.RESOLUTION_FACTORY); public static final Registry SMITHING_RESULT_PROCESSOR_FACTORY = createRegistry(Registries.SMITHING_RESULT_PROCESSOR_FACTORY); public static final Registry HITBOX_FACTORY = createRegistry(Registries.HITBOX_FACTORY); + public static final Registry RESOURCE_PACK_HOST_FACTORY = createRegistry(Registries.RESOURCE_PACK_HOST_FACTORY); private static Registry createRegistry(ResourceKey> key) { return new MappedRegistry<>(key); diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java index 7ae6e2fdb..07e1e4ef8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.loot.function.LootFunctionFactory; import net.momirealms.craftengine.core.loot.number.NumberProviderFactory; import net.momirealms.craftengine.core.pack.conflict.matcher.PathMatcherFactory; import net.momirealms.craftengine.core.pack.conflict.resolution.ResolutionFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.pack.model.ItemModelFactory; import net.momirealms.craftengine.core.pack.model.condition.ConditionPropertyFactory; import net.momirealms.craftengine.core.pack.model.rangedisptach.RangeDispatchPropertyFactory; @@ -48,4 +49,5 @@ public class Registries { public static final ResourceKey> RESOLUTION_FACTORY = new ResourceKey<>(ROOT_REGISTRY, Key.withDefaultNamespace("resolution_factory")); public static final ResourceKey> SMITHING_RESULT_PROCESSOR_FACTORY = new ResourceKey<>(ROOT_REGISTRY, Key.withDefaultNamespace("smithing_result_processor_factory")); public static final ResourceKey> HITBOX_FACTORY = new ResourceKey<>(ROOT_REGISTRY, Key.withDefaultNamespace("hitbox_factory")); + public static final ResourceKey> RESOURCE_PACK_HOST_FACTORY = new ResourceKey<>(ROOT_REGISTRY, Key.withDefaultNamespace("resource_pack_host_factory")); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/CompletableFutures.java b/core/src/main/java/net/momirealms/craftengine/core/util/CompletableFutures.java new file mode 100644 index 000000000..495097e93 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/CompletableFutures.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.core.util; + +import com.google.common.collect.ImmutableList; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collector; +import java.util.stream.Stream; + +public class CompletableFutures { + + private CompletableFutures() {} + + public static > Collector, CompletableFuture> collector() { + return Collector.of( + ImmutableList.Builder::new, + ImmutableList.Builder::add, + (l, r) -> l.addAll(r.build()), + builder -> allOf(builder.build()) + ); + } + + public static CompletableFuture allOf(Stream> futures) { + CompletableFuture[] arr = futures.toArray(CompletableFuture[]::new); + return CompletableFuture.allOf(arr); + } + + public static CompletableFuture allOf(Collection> futures) { + CompletableFuture[] arr = futures.toArray(new CompletableFuture[0]); + return CompletableFuture.allOf(arr); + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index d7349e4e7..987874a87 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.48.1 -config_version=27 +project_version=0.0.49-beta.1 +config_version=28 lang_version=4 project_group=net.momirealms latest_supported_version=1.21.5 @@ -51,7 +51,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.13 -nms_helper_version=0.59 +nms_helper_version=0.59.4 # Ignite Dependencies mixinextras_version=0.4.1 mixin_version=0.15.2+mixin.0.8.7 From d629245f63571d03327426077d1c4e2e3cc23820 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 17 Apr 2025 22:44:11 +0800 Subject: [PATCH 11/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8D1.20.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/plugin/network/PacketConsumers.java | 14 ++++++++++++-- .../craftengine/bukkit/util/Reflections.java | 4 ++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 26cf532a4..089c62248 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -1164,9 +1164,19 @@ public class PacketConsumers { try { BukkitServerPlayer player = (BukkitServerPlayer) user; String name = (String) Reflections.field$ServerboundHelloPacket$name.get(packet); - UUID uuid = (UUID) Reflections.field$ServerboundHelloPacket$uuid.get(packet); player.setName(name); - player.setUUID(uuid); + if (VersionHelper.isVersionNewerThan1_20_2()) { + UUID uuid = (UUID) Reflections.field$ServerboundHelloPacket$uuid.get(packet); + player.setUUID(uuid); + } else { + @SuppressWarnings("unchecked") + Optional uuid = (Optional) Reflections.field$ServerboundHelloPacket$uuid.get(packet); + if (uuid.isPresent()) { + player.setUUID(uuid.get()); + } else { + player.setUUID(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8))); + } + } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ServerboundHelloPacket", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index 5a4187e4a..bb3b4ba7b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -6452,8 +6452,12 @@ public class Reflections { ); public static final Field field$ServerboundHelloPacket$uuid = requireNonNull( + VersionHelper.isVersionNewerThan1_20_2() ? ReflectionUtils.getDeclaredField( clazz$ServerboundHelloPacket, UUID.class, 0 + ) : + ReflectionUtils.getDeclaredField( + clazz$ServerboundHelloPacket, Optional.class, 0 ) ); From a66241494e8b327229b21805167ffbc105a30733 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 17 Apr 2025 23:13:39 +0800 Subject: [PATCH 12/62] =?UTF-8?q?AI=E5=86=99=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/ResourcePackHost.java | 6 + .../core/pack/host/impl/LobFileHost.java | 170 ++++++++++++++++++ .../core/pack/host/impl/SelfHost.java | 5 +- .../pack/host/impl/SelfHostHttpServer.java | 14 +- 4 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java index 1da990f1d..ced5a92c6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java @@ -1,5 +1,7 @@ package net.momirealms.craftengine.core.pack.host; +import net.momirealms.craftengine.core.plugin.CraftEngine; + import java.nio.file.Path; import java.util.List; import java.util.UUID; @@ -10,4 +12,8 @@ public interface ResourcePackHost { CompletableFuture> requestResourcePackDownloadLink(UUID player); CompletableFuture upload(Path resourcePackPath); + + static Path customPackPath(String path) { + return path.startsWith(".") ? CraftEngine.instance().dataFolderPath().resolve(path) : Path.of(path); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java new file mode 100644 index 000000000..33911ce6c --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -0,0 +1,170 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class LobFileHost implements ResourcePackHost { + private Path forcedPackPath; + private String apiKey; + + private String url; + private String sha1; + private UUID uuid; + + public LobFileHost(String localFile, String apiKey) { + this.forcedPackPath = localFile == null ? null : ResourcePackHost.customPackPath(localFile); + this.apiKey = apiKey; + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + if (url == null) return CompletableFuture.completedFuture(Collections.emptyList()); + return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(url, uuid, sha1))); + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + if (this.forcedPackPath != null) resourcePackPath = forcedPackPath; + + try { + // 计算文件的 SHA1 和 SHA256 + Map hashes = calculateHashes(resourcePackPath); + String sha1Hash = hashes.get("SHA-1"); + String sha256Hash = hashes.get("SHA-256"); + + // 构建 multipart/form-data 请求 + HttpClient client = HttpClient.newHttpClient(); + String boundary = UUID.randomUUID().toString(); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://lobfile.com/api/v3/upload.php")) + .header("X-API-Key", apiKey) + .header("Content-Type", "multipart/form-data; boundary=" + boundary) + .POST(buildMultipartBody(resourcePackPath, sha256Hash, boundary)) + .build(); + + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> handleUploadResponse(response, future, sha1Hash)) + .exceptionally(ex -> { + future.completeExceptionally(ex); + return null; + }); + + } catch (IOException | NoSuchAlgorithmException e) { + future.completeExceptionally(e); + } + + return future; + } + + private Map calculateHashes(Path path) throws IOException, NoSuchAlgorithmException { + Map hashes = new HashMap<>(); + MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1"); + MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256"); + + try (InputStream is = Files.newInputStream(path); + DigestInputStream dis = new DigestInputStream(is, sha1Digest)) { + + // 同时计算 SHA-256 + DigestInputStream dis2 = new DigestInputStream(dis, sha256Digest); + + // 读取文件流触发摘要计算 + while (dis2.read() != -1) ; + + hashes.put("SHA-1", bytesToHex(sha1Digest.digest())); + hashes.put("SHA-256", bytesToHex(sha256Digest.digest())); + } + return hashes; + } + + private HttpRequest.BodyPublisher buildMultipartBody(Path filePath, String sha256Hash, String boundary) throws IOException { + List parts = new ArrayList<>(); + String filePartHeader = "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"" + filePath.getFileName() + "\"\r\n" + + "Content-Type: application/octet-stream\r\n\r\n"; + parts.add(filePartHeader.getBytes()); + + parts.add(Files.readAllBytes(filePath)); + parts.add("\r\n".getBytes()); + + String sha256Part = "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"sha_256\"\r\n\r\n" + + sha256Hash + "\r\n"; + parts.add(sha256Part.getBytes()); + + String endBoundary = "--" + boundary + "--\r\n"; + parts.add(endBoundary.getBytes()); + + return HttpRequest.BodyPublishers.ofByteArrays(parts); + } + + private void handleUploadResponse( + HttpResponse response, + CompletableFuture future, + String localSha1 + ) { + try { + if (response.statusCode() == 200) { + // 解析 JSON 响应 + Map json = parseJson(response.body()); + + if (Boolean.TRUE.equals(json.get("success"))) { + this.url = (String) json.get("url"); + this.sha1 = localSha1; + this.uuid = UUID.randomUUID(); + future.complete(null); + } else { + future.completeExceptionally(new RuntimeException((String) json.get("error"))); + } + } else { + future.completeExceptionally(new RuntimeException("Upload failed: " + response.statusCode())); + } + } catch (Exception e) { + future.completeExceptionally(e); + } + } + + // 辅助方法 + private String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + @SuppressWarnings("unchecked") + private Map parseJson(String json) { + // 这里使用简单解析(实际项目建议使用 JSON 库) + Map map = new HashMap<>(); + json = json.replaceAll("[{}\"]", ""); + for (String pair : json.split(",")) { + String[] kv = pair.split(":", 2); + if (kv.length == 2) { + String key = kv[0].trim(); + String value = kv[1].trim(); + if (key.equals("success")) { + map.put(key, Boolean.parseBoolean(value)); + } else { + map.put(key, value); + } + } + } + return map; + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index 45c4125dd..e0700aacb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -28,7 +28,7 @@ public class SelfHost implements ResourcePackHost { CompletableFuture future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { try { - SelfHostHttpServer.instance().readResourcePack(); + SelfHostHttpServer.instance().readResourcePack(resourcePackPath); future.complete(null); } catch (Exception e) { future.completeExceptionally(e); @@ -53,9 +53,6 @@ public class SelfHost implements ResourcePackHost { String protocol = (String) arguments.getOrDefault("protocol", "http"); boolean denyNonMinecraftRequest = (boolean) arguments.getOrDefault("deny-non-minecraft-request", true); String localFilePath = (String) arguments.get("local-file-path"); - if (localFilePath == null) { - throw new IllegalArgumentException("'local-file-path' argument missing for self host"); - } Map rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true); int maxRequests = 5; int resetInterval = 20_000; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 11c3aacb0..48b2ad634 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -6,6 +6,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.plugin.CraftEngine; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -65,7 +66,7 @@ public class SelfHostHttpServer { int resetInternal) { this.ip = ip; this.port = port; - this.localFilePath = selfHostPackPath(localFile); + this.localFilePath = localFile == null ? null : ResourcePackHost.customPackPath(localFile); this.denyNonMinecraft = denyNonMinecraft; this.protocol = protocol; this.rateLimit = maxRequests; @@ -110,18 +111,15 @@ public class SelfHostHttpServer { ); } - private Path selfHostPackPath(String path) { - return path.startsWith(".") ? CraftEngine.instance().dataFolderPath().resolve(path) : Path.of(path); - } - public String url() { return protocol + "://" + ip + ":" + port + "/"; } - public void readResourcePack() { + public void readResourcePack(Path path) { + if (this.localFilePath != null) path = this.localFilePath; try { - if (Files.exists(this.localFilePath)) { - this.resourcePackBytes = Files.readAllBytes(this.localFilePath); + if (Files.exists(path)) { + this.resourcePackBytes = Files.readAllBytes(path); calculateHash(); } else { this.resourcePackBytes = null; From b5ea1cfc55c86ba3652f949036750901f718a310 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 03:26:56 +0800 Subject: [PATCH 13/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0lobfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/AbstractPackManager.java | 2 - .../core/pack/host/ResourcePackHosts.java | 3 + .../core/pack/host/impl/LobFileHost.java | 103 +++++++++--------- 3 files changed, 57 insertions(+), 51 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 7757af7d9..d47cea0d8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -522,8 +522,6 @@ 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); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java index 8bb56954d..7603b86d7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.pack.host; import net.momirealms.craftengine.core.pack.host.impl.ExternalHost; +import net.momirealms.craftengine.core.pack.host.impl.LobFileHost; import net.momirealms.craftengine.core.pack.host.impl.NoneHost; import net.momirealms.craftengine.core.pack.host.impl.SelfHost; import net.momirealms.craftengine.core.registry.BuiltInRegistries; @@ -16,11 +17,13 @@ public class ResourcePackHosts { public static final Key NONE = Key.of("craftengine:none"); public static final Key SELF_HOST = Key.of("craftengine:self_host"); public static final Key EXTERNAL_HOST = Key.of("craftengine:external_host"); + public static final Key LOBFILE = Key.of("craftengine:lobfile"); static { register(NONE, NoneHost.FACTORY); register(SELF_HOST, SelfHost.FACTORY); register(EXTERNAL_HOST, ExternalHost.FACTORY); + register(LOBFILE, LobFileHost.FACTORY); } public static void register(Key key, ResourcePackHostFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 33911ce6c..928777a9b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -1,7 +1,12 @@ package net.momirealms.craftengine.core.pack.host.impl; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.GsonHelper; import java.io.IOException; import java.io.InputStream; @@ -18,8 +23,9 @@ import java.util.*; import java.util.concurrent.CompletableFuture; public class LobFileHost implements ResourcePackHost { - private Path forcedPackPath; - private String apiKey; + public static final Factory FACTORY = new Factory(); + private final Path forcedPackPath; + private final String apiKey; private String url; private String sha1; @@ -40,35 +46,34 @@ public class LobFileHost implements ResourcePackHost { public CompletableFuture upload(Path resourcePackPath) { CompletableFuture future = new CompletableFuture<>(); if (this.forcedPackPath != null) resourcePackPath = forcedPackPath; + Path finalResourcePackPath = resourcePackPath; + CraftEngine.instance().scheduler().executeAsync(() -> { + try { + Map hashes = calculateHashes(finalResourcePackPath); + String sha1Hash = hashes.get("SHA-1"); + String sha256Hash = hashes.get("SHA-256"); - try { - // 计算文件的 SHA1 和 SHA256 - Map hashes = calculateHashes(resourcePackPath); - String sha1Hash = hashes.get("SHA-1"); - String sha256Hash = hashes.get("SHA-256"); + try (HttpClient client = HttpClient.newHttpClient()) { + String boundary = UUID.randomUUID().toString(); - // 构建 multipart/form-data 请求 - HttpClient client = HttpClient.newHttpClient(); - String boundary = UUID.randomUUID().toString(); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://lobfile.com/api/v3/upload.php")) - .header("X-API-Key", apiKey) - .header("Content-Type", "multipart/form-data; boundary=" + boundary) - .POST(buildMultipartBody(resourcePackPath, sha256Hash, boundary)) - .build(); - - client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenAccept(response -> handleUploadResponse(response, future, sha1Hash)) - .exceptionally(ex -> { - future.completeExceptionally(ex); - return null; - }); - - } catch (IOException | NoSuchAlgorithmException e) { - future.completeExceptionally(e); - } + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://lobfile.com/api/v3/upload.php")) + .header("X-API-Key", apiKey) + .header("Content-Type", "multipart/form-data; boundary=" + boundary) + .POST(buildMultipartBody(finalResourcePackPath, sha256Hash, boundary)) + .build(); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> handleUploadResponse(response, future, sha1Hash)) + .exceptionally(ex -> { + future.completeExceptionally(ex); + return null; + }); + } + } catch (IOException | NoSuchAlgorithmException e) { + future.completeExceptionally(e); + } + }); return future; } @@ -79,11 +84,8 @@ public class LobFileHost implements ResourcePackHost { try (InputStream is = Files.newInputStream(path); DigestInputStream dis = new DigestInputStream(is, sha1Digest)) { - - // 同时计算 SHA-256 DigestInputStream dis2 = new DigestInputStream(dis, sha256Digest); - // 读取文件流触发摘要计算 while (dis2.read() != -1) ; hashes.put("SHA-1", bytesToHex(sha1Digest.digest())); @@ -120,13 +122,13 @@ public class LobFileHost implements ResourcePackHost { ) { try { if (response.statusCode() == 200) { - // 解析 JSON 响应 Map json = parseJson(response.body()); if (Boolean.TRUE.equals(json.get("success"))) { this.url = (String) json.get("url"); this.sha1 = localSha1; this.uuid = UUID.randomUUID(); + CraftEngine.instance().logger().info("[LobFile] Upload success! Resource pack URL: " + this.url); future.complete(null); } else { future.completeExceptionally(new RuntimeException((String) json.get("error"))); @@ -139,7 +141,6 @@ public class LobFileHost implements ResourcePackHost { } } - // 辅助方法 private String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { @@ -148,23 +149,27 @@ public class LobFileHost implements ResourcePackHost { return sb.toString(); } - @SuppressWarnings("unchecked") private Map parseJson(String json) { - // 这里使用简单解析(实际项目建议使用 JSON 库) - Map map = new HashMap<>(); - json = json.replaceAll("[{}\"]", ""); - for (String pair : json.split(",")) { - String[] kv = pair.split(":", 2); - if (kv.length == 2) { - String key = kv[0].trim(); - String value = kv[1].trim(); - if (key.equals("success")) { - map.put(key, Boolean.parseBoolean(value)); - } else { - map.put(key, value); - } - } + try { + return GsonHelper.get().fromJson( + json, + new TypeToken>() {}.getType() + ); + } catch (JsonSyntaxException e) { + throw new RuntimeException("Invalid JSON response: " + json, e); + } + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + String localFilePath = (String) arguments.get("local-file-path"); + String apiKey = (String) arguments.get("api-key"); + if (apiKey == null || apiKey.isEmpty()) { + throw new RuntimeException("Missing 'api-key' for LobFileHost"); + } + return new LobFileHost(localFilePath, apiKey); } - return map; } } \ No newline at end of file From dd43ecde2eff112a267f34ea91791977ad08e104 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 03:34:11 +0800 Subject: [PATCH 14/62] Update LobFileHost.java --- .../core/pack/host/impl/LobFileHost.java | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 928777a9b..1f68725eb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -26,6 +26,7 @@ public class LobFileHost implements ResourcePackHost { public static final Factory FACTORY = new Factory(); private final Path forcedPackPath; private final String apiKey; + private AccountInfo accountInfo; private String url; private String sha1; @@ -36,6 +37,19 @@ public class LobFileHost implements ResourcePackHost { this.apiKey = apiKey; } + public AccountInfo getAccountInfo() { + return accountInfo; + } + + public String getSpaceUsageText() { + if (accountInfo == null) return "Usage data not available"; + return String.format("Storage: %d/%d MB (%.1f%% used)", + accountInfo.getSpaceUsed() / 1_000_000, + accountInfo.getSpaceQuota() / 1_000_000, + (accountInfo.getSpaceUsed() * 100.0) / accountInfo.getSpaceQuota() + ); + } + @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { if (url == null) return CompletableFuture.completedFuture(Collections.emptyList()); @@ -77,6 +91,28 @@ public class LobFileHost implements ResourcePackHost { return future; } + public CompletableFuture fetchAccountInfo() { + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://lobfile.com/api/v3/rest/get-account-info")) + .header("X-API-Key", apiKey) + .GET() + .build(); + + return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(response -> { + if (response.statusCode() == 200) { + AccountInfo info = GsonHelper.get().fromJson(response.body(), AccountInfo.class); + if (info.isSuccess()) { + this.accountInfo = info; + return info; + } + } + throw new RuntimeException("Failed to fetch account info: " + response.statusCode()); + }); + } + } + private Map calculateHashes(Path path) throws IOException, NoSuchAlgorithmException { Map hashes = new HashMap<>(); MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1"); @@ -123,13 +159,21 @@ public class LobFileHost implements ResourcePackHost { try { if (response.statusCode() == 200) { Map json = parseJson(response.body()); - if (Boolean.TRUE.equals(json.get("success"))) { this.url = (String) json.get("url"); this.sha1 = localSha1; this.uuid = UUID.randomUUID(); CraftEngine.instance().logger().info("[LobFile] Upload success! Resource pack URL: " + this.url); - future.complete(null); + fetchAccountInfo() + .thenAccept(info -> { + CraftEngine.instance().logger().info("[LobFile] Account Usage Updated: " + getSpaceUsageText()); + future.complete(null); + }) + .exceptionally(ex -> { + CraftEngine.instance().logger().warn("[LobFile] Usage check failed (upload still succeeded): ", ex); + future.complete(null); + return null; + }); } else { future.completeExceptionally(new RuntimeException((String) json.get("error"))); } @@ -172,4 +216,31 @@ public class LobFileHost implements ResourcePackHost { return new LobFileHost(localFilePath, apiKey); } } + + public static class AccountInfo { + private boolean success; + private Map account_info; + private Map account_limits; + private Map account_usage; + + public String getEmail() { + return (String) account_info.get("email"); + } + + public int getSpaceQuota() { + return account_limits.getOrDefault("space_quota", 0); + } + + public int getSpaceUsed() { + return account_usage.getOrDefault("space_used", 0); + } + + public int getSlotsUsed() { + return account_usage.getOrDefault("slots_used", 0); + } + + public boolean isSuccess() { + return success; + } + } } \ No newline at end of file From 0d81a962e59137b1295449a7accd9ba24f96b17c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 03:39:34 +0800 Subject: [PATCH 15/62] =?UTF-8?q?=E8=AE=B0=E5=BD=95=E8=80=97=E6=97=B6?= =?UTF-8?q?=E5=92=8C=E5=AD=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/impl/LobFileHost.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 1f68725eb..2d7a96f4d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -59,8 +59,11 @@ public class LobFileHost implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { CompletableFuture future = new CompletableFuture<>(); + long totalStartTime = System.currentTimeMillis(); + if (this.forcedPackPath != null) resourcePackPath = forcedPackPath; Path finalResourcePackPath = resourcePackPath; + CraftEngine.instance().scheduler().executeAsync(() -> { try { Map hashes = calculateHashes(finalResourcePackPath); @@ -77,14 +80,29 @@ public class LobFileHost implements ResourcePackHost { .POST(buildMultipartBody(finalResourcePackPath, sha256Hash, boundary)) .build(); + long uploadStart = System.currentTimeMillis(); + CraftEngine.instance().logger().info("[LobFile] Starting file upload..."); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenAccept(response -> handleUploadResponse(response, future, sha1Hash)) + .thenAccept(response -> { + long uploadTime = System.currentTimeMillis() - uploadStart; + CraftEngine.instance().logger().info( + "[LobFile] Upload request completed in " + uploadTime + "ms"); + + handleUploadResponse(response, future, sha1Hash); + }) .exceptionally(ex -> { + long totalTime = System.currentTimeMillis() - totalStartTime; + CraftEngine.instance().logger().severe( + "[LobFile] Upload failed after " + totalTime + "ms", ex); future.completeExceptionally(ex); return null; }); } } catch (IOException | NoSuchAlgorithmException e) { + long totalTime = System.currentTimeMillis() - totalStartTime; + CraftEngine.instance().logger().severe( + "[LobFile] Upload preparation failed after " + totalTime + "ms", e); future.completeExceptionally(e); } }); From 0d8f80d5b8f3bc58429be270bbb921d020e18b64 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 03:57:04 +0800 Subject: [PATCH 16/62] =?UTF-8?q?=E6=94=B9=E5=96=84=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/pack/BukkitPackManager.java | 3 -- .../core/pack/host/impl/LobFileHost.java | 51 +++++++++++++++++-- .../core/pack/host/impl/SelfHost.java | 4 ++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index b437a7c6b..74b5369bb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -73,9 +73,6 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { this.modifyServerSettings(); } } - if (CraftEngine.instance().isInitializing()) { - resourcePackHost().upload(this.resourcePackPath()); - } } public void modifyServerSettings() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 2d7a96f4d..56a8136f0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -10,12 +10,14 @@ import net.momirealms.craftengine.core.util.GsonHelper; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -35,10 +37,52 @@ public class LobFileHost implements ResourcePackHost { public LobFileHost(String localFile, String apiKey) { this.forcedPackPath = localFile == null ? null : ResourcePackHost.customPackPath(localFile); this.apiKey = apiKey; + this.readCacheFromDisk(); } - public AccountInfo getAccountInfo() { - return accountInfo; + public void readCacheFromDisk() { + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("lobfile.cache"); + if (!Files.exists(cachePath)) return; + + try (InputStream is = Files.newInputStream(cachePath)) { + Map cache = GsonHelper.get().fromJson( + new InputStreamReader(is), + new TypeToken>(){}.getType() + ); + + this.url = cache.get("url"); + this.sha1 = cache.get("sha1"); + + String uuidString = cache.get("uuid"); + if (uuidString != null && !uuidString.isEmpty()) { + this.uuid = UUID.fromString(uuidString); + } + + CraftEngine.instance().logger().info("[LobFile] Loaded cached resource pack info"); + } catch (Exception e) { + CraftEngine.instance().logger().warn( + "[LobFile] Failed to read cache file: " + e.getMessage()); + } + } + + public void saveCacheToDisk() { + Map cache = new HashMap<>(); + cache.put("url", this.url); + cache.put("sha1", this.sha1); + cache.put("uuid", this.uuid != null ? this.uuid.toString() : ""); + + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("lobfile.cache"); + try { + Files.writeString( + cachePath, + GsonHelper.get().toJson(cache), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ); + } catch (IOException e) { + CraftEngine.instance().logger().warn( + "[LobFile] Failed to save cache: " + e.getMessage()); + } } public String getSpaceUsageText() { @@ -181,10 +225,11 @@ public class LobFileHost implements ResourcePackHost { this.url = (String) json.get("url"); this.sha1 = localSha1; this.uuid = UUID.randomUUID(); + saveCacheToDisk(); CraftEngine.instance().logger().info("[LobFile] Upload success! Resource pack URL: " + this.url); fetchAccountInfo() .thenAccept(info -> { - CraftEngine.instance().logger().info("[LobFile] Account Usage Updated: " + getSpaceUsageText()); + CraftEngine.instance().logger().info("[LobFile] Account usage updated: " + getSpaceUsageText()); future.complete(null); }) .exceptionally(ex -> { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index e0700aacb..b50b1d34d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -16,6 +16,10 @@ public class SelfHost implements ResourcePackHost { public static final Factory FACTORY = new Factory(); private static final SelfHost INSTANCE = new SelfHost(); + public SelfHost() { + SelfHostHttpServer.instance().readResourcePack(CraftEngine.instance().packManager().resourcePackPath()); + } + @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { ResourcePackDownloadData data = SelfHostHttpServer.instance().generateOneTimeUrl(); From efa58054e0537bb4f91e14bd3898f203b2599831 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 04:22:26 +0800 Subject: [PATCH 17/62] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=20Amazon=20S3=20=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/build.gradle.kts | 2 + .../main/resources/craft-engine.properties | 33 +- core/build.gradle.kts | 4 + .../core/pack/host/ResourcePackHosts.java | 3 + .../core/pack/host/impl/S3Host.java | 200 +++++++++++ .../craftengine/core/plugin/CraftEngine.java | 33 +- .../core/plugin/dependency/Dependencies.java | 335 ++++++++++++++++++ gradle.properties | 3 + 8 files changed, 611 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index 49c1cecf5..b6e21dd81 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -75,5 +75,7 @@ tasks { relocate("org.yaml.snakeyaml", "net.momirealms.craftengine.libraries.snakeyaml") relocate("org.ahocorasick", "net.momirealms.craftengine.libraries.ahocorasick") relocate("net.jpountz", "net.momirealms.craftengine.libraries.jpountz") + relocate("software.amazon.awssdk", "net.momirealms.craftengine.libraries.awssdk") + relocate("software.amazon.eventstream", "net.momirealms.craftengine.libraries.eventstream") } } diff --git a/bukkit/loader/src/main/resources/craft-engine.properties b/bukkit/loader/src/main/resources/craft-engine.properties index 96d51b50e..826fd18e4 100644 --- a/bukkit/loader/src/main/resources/craft-engine.properties +++ b/bukkit/loader/src/main/resources/craft-engine.properties @@ -28,4 +28,35 @@ adventure-text-serializer-json=${adventure_bundle_version} adventure-text-serializer-json-legacy-impl=${adventure_bundle_version} netty-codec-http=${netty_version} ahocorasick=${ahocorasick_version} -lz4=${lz4_version} \ No newline at end of file +lz4=${lz4_version} +netty-codec-http2=${netty_version} +reactive-streams=${reactive_streams_version} +amazon-sdk-s3=${amazon_awssdk_version} +amazon-sdk-netty-nio-client=${amazon_awssdk_version} +amazon-sdk-core=${amazon_awssdk_version} +amazon-sdk-auth=${amazon_awssdk_version} +amazon-sdk-regions=${amazon_awssdk_version} +amazon-sdk-identity-spi=${amazon_awssdk_version} +amazon-sdk-http-client-spi=${amazon_awssdk_version} +amazon-sdk-protocol-core=${amazon_awssdk_version} +amazon-sdk-aws-xml-protocol=${amazon_awssdk_version} +amazon-sdk-json-utils=${amazon_awssdk_version} +amazon-sdk-aws-core=${amazon_awssdk_version} +amazon-sdk-utils=${amazon_awssdk_version} +amazon-sdk-annotations=${amazon_awssdk_version} +amazon-sdk-crt-core=${amazon_awssdk_version} +amazon-sdk-checksums=${amazon_awssdk_version} +amazon-sdk-profiles=${amazon_awssdk_version} +amazon-sdk-retries=${amazon_awssdk_version} +amazon-sdk-endpoints-spi=${amazon_awssdk_version} +amazon-sdk-arns=${amazon_awssdk_version} +amazon-sdk-aws-query-protocol=${amazon_awssdk_version} +amazon-sdk-http-auth-aws=${amazon_awssdk_version} +amazon-sdk-http-auth-spi=${amazon_awssdk_version} +amazon-sdk-http-auth=${amazon_awssdk_version} +amazon-sdk-http-auth-aws-eventstream=${amazon_awssdk_version} +amazon-sdk-checksums-spi=${amazon_awssdk_version} +amazon-sdk-retries-spi=${amazon_awssdk_version} +amazon-sdk-metrics-spi=${amazon_awssdk_version} +amazon-sdk-third-party-jackson-core=${amazon_awssdk_version} +amazon-sdk-eventstream=${amazon_awssdk_eventstream_version} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 896447ad4..42965a69a 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -51,6 +51,8 @@ dependencies { compileOnly("com.mojang:datafixerupper:${rootProject.properties["datafixerupper_version"]}") // Aho-Corasick java implementation compileOnly("org.ahocorasick:ahocorasick:${rootProject.properties["ahocorasick_version"]}") + // Amazon S3 + compileOnly("software.amazon.awssdk:s3:${rootProject.properties["amazon_awssdk_version"]}") } java { @@ -80,6 +82,8 @@ tasks { relocate("org.ahocorasick", "net.momirealms.craftengine.libraries.ahocorasick") relocate("net.momirealms.sparrow.nbt", "net.momirealms.craftengine.libraries.nbt") relocate("net.jpountz", "net.momirealms.craftengine.libraries.jpountz") // lz4 + relocate("software.amazon.awssdk", "net.momirealms.craftengine.libraries.awssdk") // awssdk + relocate("software.amazon.eventstream", "net.momirealms.craftengine.libraries.eventstream") // awssdk } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java index 7603b86d7..6e0cbb0be 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.pack.host; import net.momirealms.craftengine.core.pack.host.impl.ExternalHost; import net.momirealms.craftengine.core.pack.host.impl.LobFileHost; import net.momirealms.craftengine.core.pack.host.impl.NoneHost; +import net.momirealms.craftengine.core.pack.host.impl.S3Host; import net.momirealms.craftengine.core.pack.host.impl.SelfHost; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; @@ -18,12 +19,14 @@ public class ResourcePackHosts { public static final Key SELF_HOST = Key.of("craftengine:self_host"); public static final Key EXTERNAL_HOST = Key.of("craftengine:external_host"); public static final Key LOBFILE = Key.of("craftengine:lobfile"); + public static final Key S3_HOST = Key.of("craftengine:s3_host"); static { register(NONE, NoneHost.FACTORY); register(SELF_HOST, SelfHost.FACTORY); register(EXTERNAL_HOST, ExternalHost.FACTORY); register(LOBFILE, LobFileHost.FACTORY); + register(S3_HOST, S3Host.FACTORY); } public static void register(Key key, ResourcePackHostFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java new file mode 100644 index 000000000..8bed0a7a0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -0,0 +1,200 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.MiscUtils; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.auth.signer.AwsS3V4Signer; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; +import software.amazon.awssdk.services.s3.model.HeadObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class S3Host implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + private final S3AsyncClient s3AsyncClient; + private final S3Presigner presigner; + private final String bucket; + private final String uploadPath; + private final String uploadFileName; + private final String cdnDomain; + private final String cdnProtocol; + private final Duration validity; + + public S3Host( + S3AsyncClient s3AsyncClient, + S3Presigner presigner, + String bucket, + String uploadPath, + String uploadFileName, + String cdnDomain, + String cdnProtocol, + Duration validity + ) { + this.s3AsyncClient = s3AsyncClient; + this.presigner = presigner; + this.bucket = bucket; + this.uploadPath = uploadPath; + this.uploadFileName = uploadFileName; + this.cdnDomain = cdnDomain; + this.cdnProtocol = cdnProtocol; + this.validity = validity; + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + String objectKey = uploadPath + uploadFileName; + + return s3AsyncClient.headObject(HeadObjectRequest.builder() + .bucket(bucket) + .key(objectKey) + .build()) + .thenApply(headResponse -> { + String sha1 = headResponse.metadata().get("sha1"); + if (sha1 == null) { + throw new IllegalStateException("SHA1 metadata missing for object: " + objectKey); + } + GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() + .signatureDuration(validity) + .getObjectRequest(b -> b.bucket(bucket).key(objectKey)) + .build(); + return Collections.singletonList( + ResourcePackDownloadData.of( + replaceWithCdnUrl(presigner.presignGetObject(presignRequest).url()), + UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), sha1 + ) + ); + }); + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + String objectKey = uploadPath + uploadFileName; + String sha1 = calculateLocalFileSha1(resourcePackPath); + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucket) + .key(objectKey) + .metadata(Map.of("sha1", sha1)) + .build(); + return s3AsyncClient.putObject(putObjectRequest, AsyncRequestBody.fromFile(resourcePackPath)) + .thenApply(response -> { + CraftEngine.instance().logger().info("Uploaded resource pack to S3: " + objectKey); + return null; + }); + } + + private String replaceWithCdnUrl(URL originalUrl) { + if (cdnDomain == null) return originalUrl.toString(); + return cdnProtocol + "://" + cdnDomain + + originalUrl.getPath() + + (originalUrl.getQuery() != null ? "?" + originalUrl.getQuery() : ""); + } + + private String calculateLocalFileSha1(Path filePath) { + try (InputStream is = Files.newInputStream(filePath)) { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] buffer = new byte[8192]; + int len; + while ((len = is.read(buffer)) != -1) { + md.update(buffer, 0, len); + } + byte[] digest = md.digest(); + return HexFormat.of().formatHex(digest); + } catch (IOException | NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to calculate SHA1", e); + } + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + @SuppressWarnings("deprecation") + public ResourcePackHost create(Map arguments) { + String endpoint = (String) arguments.get("endpoint"); + if (endpoint == null || endpoint.isEmpty()) { + throw new IllegalArgumentException("'endpoint' cannot be empty for S3 host"); + } + String protocol = (String) arguments.getOrDefault("protocol", "https"); + boolean usePathStyle = (boolean) arguments.getOrDefault("path-style", false); + String bucket = (String) arguments.get("bucket"); + if (bucket == null || bucket.isEmpty()) { + throw new IllegalArgumentException("'bucket' cannot be empty for S3 host"); + } + String region = (String) arguments.getOrDefault("region", "auto"); + String accessKeyId = (String) arguments.get("access-key-id"); + if (accessKeyId == null || accessKeyId.isEmpty()) { + throw new IllegalArgumentException("'access-key-id' cannot be empty for S3 host"); + } + String accessKeySecret = (String) arguments.get("access-key-secret"); + if (accessKeySecret == null || accessKeySecret.isEmpty()) { + throw new IllegalArgumentException("'access-key-secret' cannot be empty for S3 host"); + } + String uploadPath = (String) arguments.getOrDefault("upload-path", ""); + String uploadFileName = (String) arguments.getOrDefault("upload-file-name", "resource_pack.zip"); + boolean useLegacySignature = (boolean) arguments.getOrDefault("use-legacy-signature", true); + Duration validity = Duration.ofSeconds((int) arguments.getOrDefault("validity", 10)); + + Map cdn = MiscUtils.castToMap(arguments.get("cdn"), true); + String cdnDomain = null; + String cdnProtocol = "https"; + if (cdn != null) { + cdnDomain = (String) cdn.get("domain"); + cdnProtocol = (String) cdn.getOrDefault("protocol", "https"); + } + + AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKeyId, accessKeySecret); + + S3AsyncClientBuilder s3AsyncClientBuilder = S3AsyncClient.builder() + .endpointOverride(URI.create(protocol + "://" + endpoint)) + .region(Region.of(region)) + .credentialsProvider(StaticCredentialsProvider.create(credentials)) + .serviceConfiguration(b -> b.pathStyleAccessEnabled(usePathStyle)); + + if (useLegacySignature) { + s3AsyncClientBuilder.overrideConfiguration(b -> b + .putAdvancedOption(SdkAdvancedClientOption.SIGNER, AwsS3V4Signer.create()) + ); + } + + S3AsyncClient s3AsyncClient = s3AsyncClientBuilder.build(); + + S3Presigner presigner = S3Presigner.builder() + .endpointOverride(URI.create(protocol + "://" + endpoint)) + .region(Region.of(region)) + .credentialsProvider(StaticCredentialsProvider.create(credentials)) + .build(); + + return new S3Host( + s3AsyncClient, + presigner, + bucket, + uploadPath, + uploadFileName, + cdnDomain, + cdnProtocol, + validity + ); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 5c970821c..31cd4fca5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -292,7 +292,38 @@ public abstract class CraftEngine implements Plugin { Dependencies.TEXT_SERIALIZER_GSON, Dependencies.TEXT_SERIALIZER_GSON_LEGACY, Dependencies.TEXT_SERIALIZER_JSON, Dependencies.AHO_CORASICK, - Dependencies.LZ4 + Dependencies.LZ4, + Dependencies.NETTY_HTTP2, + Dependencies.REACTIVE_STREAMS, + Dependencies.AMAZON_AWSSDK_S3, + Dependencies.AMAZON_AWSSDK_NETTY_NIO_CLIENT, + Dependencies.AMAZON_AWSSDK_SDK_CORE, + Dependencies.AMAZON_AWSSDK_AUTH, + Dependencies.AMAZON_AWSSDK_REGIONS, + Dependencies.AMAZON_AWSSDK_IDENTITY_SPI, + Dependencies.AMAZON_AWSSDK_HTTP_CLIENT_SPI, + Dependencies.AMAZON_AWSSDK_PROTOCOL_CORE, + Dependencies.AMAZON_AWSSDK_AWS_XML_PROTOCOL, + Dependencies.AMAZON_AWSSDK_JSON_UTILS, + Dependencies.AMAZON_AWSSDK_AWS_CORE, + Dependencies.AMAZON_AWSSDK_UTILS, + Dependencies.AMAZON_AWSSDK_ANNOTATIONS, + Dependencies.AMAZON_AWSSDK_CRT_CORE, + Dependencies.AMAZON_AWSSDK_CHECKSUMS, + Dependencies.AMAZON_EVENTSTREAM, + Dependencies.AMAZON_AWSSDK_PROFILES, + Dependencies.AMAZON_AWSSDK_RETRIES, + Dependencies.AMAZON_AWSSDK_ENDPOINTS_SPI, + Dependencies.AMAZON_AWSSDK_ARNS, + Dependencies.AMAZON_AWSSDK_AWS_QUERY_PROTOCOL, + Dependencies.AMAZON_AWSSDK_HTTP_AUTH_AWS, + Dependencies.AMAZON_AWSSDK_HTTP_AUTH_SPI, + Dependencies.AMAZON_AWSSDK_HTTP_AUTH, + Dependencies.AMAZON_AWSSDK_HTTP_AUTH_AWS_EVENTSTREAM, + Dependencies.AMAZON_AWSSDK_CHECKSUMS_SPI, + Dependencies.AMAZON_AWSSDK_RETRIES_SPI, + Dependencies.AMAZON_AWSSDK_METRICS_SPI, + Dependencies.AMAZON_AWSSDK_THIRD_PARTY_JACKSON_CORE ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 6a70db819..9699da54b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -228,4 +228,339 @@ public class Dependencies { "lz4-java", List.of(Relocation.of("jpountz", "net{}jpountz")) ); + + public static final Dependency NETTY_HTTP2 = new Dependency( + "netty-codec-http2", + "io{}netty", + "netty-codec-http2", + "netty-codec-http2", + Collections.emptyList() + ); + + public static final Dependency REACTIVE_STREAMS = new Dependency( + "reactive-streams", + "org{}reactivestreams", + "reactive-streams", + "reactive-streams", + List.of(Relocation.of("reactivestreams", "org{}reactivestreams")) + ); + + public static final Dependency AMAZON_AWSSDK_S3 = new Dependency( + "amazon-sdk-s3", + "software{}amazon{}awssdk", + "s3", + "amazon-s3", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_NETTY_NIO_CLIENT = new Dependency( + "amazon-sdk-netty-nio-client", + "software{}amazon{}awssdk", + "netty-nio-client", + "amazon-netty-nio-client", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_SDK_CORE = new Dependency( + "amazon-sdk-core", + "software{}amazon{}awssdk", + "sdk-core", + "amazon-sdk-core", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_AUTH = new Dependency( + "amazon-sdk-auth", + "software{}amazon{}awssdk", + "auth", + "amazon-auth", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_REGIONS = new Dependency( + "amazon-sdk-regions", + "software{}amazon{}awssdk", + "regions", + "amazon-regions", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_IDENTITY_SPI = new Dependency( + "amazon-sdk-identity-spi", + "software{}amazon{}awssdk", + "identity-spi", + "amazon-identity-spi", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_HTTP_CLIENT_SPI = new Dependency( + "amazon-sdk-http-client-spi", + "software{}amazon{}awssdk", + "http-client-spi", + "amazon-http-client-spi", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_PROTOCOL_CORE = new Dependency( + "amazon-sdk-protocol-core", + "software{}amazon{}awssdk", + "protocol-core", + "amazon-protocol-core", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_AWS_XML_PROTOCOL = new Dependency( + "amazon-sdk-aws-xml-protocol", + "software{}amazon{}awssdk", + "aws-xml-protocol", + "amazon-aws-xml-protocol", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_JSON_UTILS = new Dependency( + "amazon-sdk-json-utils", + "software{}amazon{}awssdk", + "json-utils", + "amazon-json-utils", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_AWS_CORE = new Dependency( + "amazon-sdk-aws-core", + "software{}amazon{}awssdk", + "aws-core", + "amazon-aws-core", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_UTILS = new Dependency( + "amazon-sdk-utils", + "software{}amazon{}awssdk", + "utils", + "amazon-utils", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_ANNOTATIONS = new Dependency( + "amazon-sdk-annotations", + "software{}amazon{}awssdk", + "annotations", + "amazon-annotations", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_CRT_CORE = new Dependency( + "amazon-sdk-crt-core", + "software{}amazon{}awssdk", + "crt-core", + "amazon-crt-core", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_CHECKSUMS = new Dependency( + "amazon-sdk-checksums", + "software{}amazon{}awssdk", + "checksums", + "amazon-checksums", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_EVENTSTREAM = new Dependency( + "amazon-sdk-eventstream", + "software{}amazon{}eventstream", + "eventstream", + "amazon-eventstream", + List.of( + Relocation.of("eventstream", "software{}amazon{}eventstream"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_PROFILES = new Dependency( + "amazon-sdk-profiles", + "software{}amazon{}awssdk", + "profiles", + "amazon-profiles", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_RETRIES = new Dependency( + "amazon-sdk-retries", + "software{}amazon{}awssdk", + "retries", + "amazon-retries", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_ENDPOINTS_SPI = new Dependency( + "amazon-sdk-endpoints-spi", + "software{}amazon{}awssdk", + "endpoints-spi", + "amazon-endpoints-spi", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_ARNS = new Dependency( + "amazon-sdk-arns", + "software{}amazon{}awssdk", + "arns", + "amazon-arns", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_AWS_QUERY_PROTOCOL = new Dependency( + "amazon-sdk-aws-query-protocol", + "software{}amazon{}awssdk", + "aws-query-protocol", + "amazon-aws-query-protocol", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_HTTP_AUTH_AWS = new Dependency( + "amazon-sdk-http-auth-aws", + "software{}amazon{}awssdk", + "http-auth-aws", + "amazon-http-auth-aws", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_HTTP_AUTH_SPI = new Dependency( + "amazon-sdk-http-auth-spi", + "software{}amazon{}awssdk", + "http-auth-spi", + "amazon-http-auth-spi", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_HTTP_AUTH = new Dependency( + "amazon-sdk-http-auth", + "software{}amazon{}awssdk", + "http-auth", + "amazon-http-auth", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_HTTP_AUTH_AWS_EVENTSTREAM = new Dependency( + "amazon-sdk-http-auth-aws-eventstream", + "software{}amazon{}awssdk", + "http-auth-aws-eventstream", + "amazon-http-auth-aws-eventstream", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_CHECKSUMS_SPI = new Dependency( + "amazon-sdk-checksums-spi", + "software{}amazon{}awssdk", + "checksums-spi", + "amazon-checksums-spi", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_RETRIES_SPI = new Dependency( + "amazon-sdk-retries-spi", + "software{}amazon{}awssdk", + "retries-spi", + "amazon-retries-spi", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_METRICS_SPI = new Dependency( + "amazon-sdk-metrics-spi", + "software{}amazon{}awssdk", + "metrics-spi", + "amazon-metrics-spi", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); + + public static final Dependency AMAZON_AWSSDK_THIRD_PARTY_JACKSON_CORE = new Dependency( + "amazon-sdk-third-party-jackson-core", + "software{}amazon{}awssdk", + "third-party-jackson-core", + "amazon-third-party-jackson-core", + List.of( + Relocation.of("awssdk", "software{}amazon{}awssdk"), + Relocation.of("reactivestreams", "org{}reactivestreams") + ) + ); } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 987874a87..270503e76 100644 --- a/gradle.properties +++ b/gradle.properties @@ -52,6 +52,9 @@ ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.13 nms_helper_version=0.59.4 +reactive_streams_version=1.0.4 +amazon_awssdk_version=2.31.23 +amazon_awssdk_eventstream_version=1.0.1 # Ignite Dependencies mixinextras_version=0.4.1 mixin_version=0.15.2+mixin.0.8.7 From adc2ab2c1d37dd08b32c2e6ef0e3de05ebf2b476 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 04:38:26 +0800 Subject: [PATCH 18/62] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=20Amazon=20S3=20=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/core/pack/host/impl/S3Host.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index 8bed0a7a0..5a21be805 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -41,6 +41,7 @@ public class S3Host implements ResourcePackHost { private final String cdnDomain; private final String cdnProtocol; private final Duration validity; + private final Path localFilePath; public S3Host( S3AsyncClient s3AsyncClient, @@ -50,7 +51,8 @@ public class S3Host implements ResourcePackHost { String uploadFileName, String cdnDomain, String cdnProtocol, - Duration validity + Duration validity, + String localFilePath ) { this.s3AsyncClient = s3AsyncClient; this.presigner = presigner; @@ -60,6 +62,7 @@ public class S3Host implements ResourcePackHost { this.cdnDomain = cdnDomain; this.cdnProtocol = cdnProtocol; this.validity = validity; + this.localFilePath = localFilePath == null ? null : ResourcePackHost.customPackPath(localFilePath); } @Override @@ -90,6 +93,7 @@ public class S3Host implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { + if (this.localFilePath != null) resourcePackPath = this.localFilePath; String objectKey = uploadPath + uploadFileName; String sha1 = calculateLocalFileSha1(resourcePackPath); PutObjectRequest putObjectRequest = PutObjectRequest.builder() @@ -152,6 +156,7 @@ public class S3Host implements ResourcePackHost { } String uploadPath = (String) arguments.getOrDefault("upload-path", ""); String uploadFileName = (String) arguments.getOrDefault("upload-file-name", "resource_pack.zip"); + String localFilePath = (String) arguments.get("local-file-path"); boolean useLegacySignature = (boolean) arguments.getOrDefault("use-legacy-signature", true); Duration validity = Duration.ofSeconds((int) arguments.getOrDefault("validity", 10)); @@ -193,7 +198,8 @@ public class S3Host implements ResourcePackHost { uploadFileName, cdnDomain, cdnProtocol, - validity + validity, + localFilePath ); } } From 62ce52ae4ffe2afe7d86fc675d81730394c9d1d6 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 06:31:06 +0800 Subject: [PATCH 19/62] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=20API=20=E6=89=98=E7=AE=A1=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20S3=20=E6=89=98=E7=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/ResourcePackHosts.java | 8 +- .../core/pack/host/impl/CustomApiHost.java | 128 ++++++++++++++++++ .../core/pack/host/impl/S3Host.java | 36 ++++- 3 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java index 6e0cbb0be..8f54ecb7b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -1,10 +1,6 @@ package net.momirealms.craftengine.core.pack.host; -import net.momirealms.craftengine.core.pack.host.impl.ExternalHost; -import net.momirealms.craftengine.core.pack.host.impl.LobFileHost; -import net.momirealms.craftengine.core.pack.host.impl.NoneHost; -import net.momirealms.craftengine.core.pack.host.impl.S3Host; -import net.momirealms.craftengine.core.pack.host.impl.SelfHost; +import net.momirealms.craftengine.core.pack.host.impl.*; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.Registries; @@ -20,6 +16,7 @@ public class ResourcePackHosts { public static final Key EXTERNAL_HOST = Key.of("craftengine:external_host"); public static final Key LOBFILE = Key.of("craftengine:lobfile"); public static final Key S3_HOST = Key.of("craftengine:s3_host"); + public static final Key CUSTOM_API_HOST = Key.of("craftengine:custom_api_host"); static { register(NONE, NoneHost.FACTORY); @@ -27,6 +24,7 @@ public class ResourcePackHosts { register(EXTERNAL_HOST, ExternalHost.FACTORY); register(LOBFILE, LobFileHost.FACTORY); register(S3_HOST, S3Host.FACTORY); + register(CUSTOM_API_HOST, CustomApiHost.FACTORY); } public static void register(Key key, ResourcePackHostFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java new file mode 100644 index 000000000..e6eb10e4a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java @@ -0,0 +1,128 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.GsonHelper; + +import java.io.FileNotFoundException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class CustomApiHost implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + private final URL apiUrl; + private final Path localFilePath; + + public CustomApiHost(URL apiUrl, String localFilePath) { + this.apiUrl = apiUrl; + this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + CompletableFuture> future = new CompletableFuture<>(); + CraftEngine.instance().scheduler().executeAsync(() -> { + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(apiUrl + "/api/v1/get-download-link?uuid=" + player)) + .GET() + .build(); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + if (response.statusCode() == 200) { + Map jsonData = parseJson(response.body()); + String url = (String) jsonData.get("url"); + String sha1 = (String) jsonData.get("sha1"); + UUID uuid = UUID.fromString(sha1); + future.complete(List.of(new ResourcePackDownloadData(url, uuid, sha1))); + } + }) + .exceptionally(ex -> { + CraftEngine.instance().logger().warn("[CustomApi] Get resource pack download link failed", ex); + future.completeExceptionally(ex); + return null; + }); + } + }); + return future; + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + if (this.localFilePath != null) resourcePackPath = this.localFilePath; + Path finalResourcePackPath = resourcePackPath; + CraftEngine.instance().scheduler().executeAsync(() -> { + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(apiUrl + "/api/v1/upload-resource-pack")) + .header("Content-Type", "application/octet-stream") + .PUT(HttpRequest.BodyPublishers.ofFile(finalResourcePackPath)) + .build(); + long uploadStart = System.currentTimeMillis(); + CraftEngine.instance().logger().info("[CustomApi] Starting file upload..."); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + long uploadTime = System.currentTimeMillis() - uploadStart; + CraftEngine.instance().logger().info( + "[CustomApi] Upload request completed in " + uploadTime + "ms"); + if (response.statusCode() == 200) { + future.complete(null); + } else { + future.completeExceptionally(new RuntimeException("Upload failed with status code: " + response.statusCode())); + } + }) + .exceptionally(ex -> { + CraftEngine.instance().logger().warn("[CustomApi] Upload resource pack failed", ex); + future.completeExceptionally(ex); + return null; + }); + } catch (FileNotFoundException e) { + CraftEngine.instance().logger().warn("[CustomApi] Resource pack not found: " + finalResourcePackPath); + future.completeExceptionally(e); + } + }); + return future; + } + + private Map parseJson(String json) { + try { + return GsonHelper.get().fromJson( + json, + new TypeToken>() {}.getType() + ); + } catch (JsonSyntaxException e) { + throw new RuntimeException("Invalid JSON response: " + json, e); + } + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + String apiUrl = (String) arguments.get("api-url"); + String localFilePath = (String) arguments.get("local-file-path"); + if (apiUrl == null || apiUrl.isEmpty()) { + throw new IllegalArgumentException("'api-url' cannot be empty for custom api host"); + } + try { + return new CustomApiHost(URI.create(apiUrl).toURL(), localFilePath); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid 'api-url' for custom api host: " + apiUrl, e); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index 5a21be805..d2ad64734 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -14,6 +14,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; import software.amazon.awssdk.services.s3.model.HeadObjectRequest; +import software.amazon.awssdk.services.s3.model.NoSuchKeyException; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; @@ -30,6 +31,7 @@ import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; public class S3Host implements ResourcePackHost { public static final Factory FACTORY = new Factory(); @@ -73,10 +75,18 @@ public class S3Host implements ResourcePackHost { .bucket(bucket) .key(objectKey) .build()) - .thenApply(headResponse -> { + .handle((headResponse, exception) -> { + if (exception != null) { + Throwable cause = exception.getCause(); + if (cause instanceof NoSuchKeyException) { + return Collections.emptyList(); + } else { + throw new CompletionException("Failed to request resource pack", cause); + } + } String sha1 = headResponse.metadata().get("sha1"); if (sha1 == null) { - throw new IllegalStateException("SHA1 metadata missing for object: " + objectKey); + throw new CompletionException(new IllegalStateException("SHA1 metadata missing for object: " + objectKey)); } GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() .signatureDuration(validity) @@ -85,7 +95,8 @@ public class S3Host implements ResourcePackHost { return Collections.singletonList( ResourcePackDownloadData.of( replaceWithCdnUrl(presigner.presignGetObject(presignRequest).url()), - UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), sha1 + UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), + sha1 ) ); }); @@ -101,9 +112,24 @@ public class S3Host implements ResourcePackHost { .key(objectKey) .metadata(Map.of("sha1", sha1)) .build(); + long uploadStart = System.currentTimeMillis(); + CraftEngine.instance().logger().info("[S3] Starting file upload..."); return s3AsyncClient.putObject(putObjectRequest, AsyncRequestBody.fromFile(resourcePackPath)) - .thenApply(response -> { - CraftEngine.instance().logger().info("Uploaded resource pack to S3: " + objectKey); + .handle((response, exception) -> { + if (exception != null) { + Throwable cause = exception instanceof CompletionException ? + exception.getCause() : + exception; + CraftEngine.instance().logger().warn( + "[S3] Upload to " + objectKey + " failed! Reason: " + + cause.getClass().getSimpleName() + " - " + cause.getMessage() + ); + throw new CompletionException("Resource pack upload failed", cause); + } + CraftEngine.instance().logger().info( + "[S3] Upload to " + objectKey + " complete! Took " + + (System.currentTimeMillis() - uploadStart) + "ms" + ); return null; }); } From e3c24087a97b11a32f41f4c29323c180d39ff9c3 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 06:33:51 +0800 Subject: [PATCH 20/62] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96=20S3=20?= =?UTF-8?q?=E6=89=98=E7=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../momirealms/craftengine/core/pack/host/impl/S3Host.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index d2ad64734..0a0c75aa9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -81,11 +81,16 @@ public class S3Host implements ResourcePackHost { if (cause instanceof NoSuchKeyException) { return Collections.emptyList(); } else { + CraftEngine.instance().logger().warn( + "[S3] Requesting resource pack failed! Reason: " + + cause.getClass().getSimpleName() + " - " + cause.getMessage() + ); throw new CompletionException("Failed to request resource pack", cause); } } String sha1 = headResponse.metadata().get("sha1"); if (sha1 == null) { + CraftEngine.instance().logger().warn("[S3] SHA1 metadata missing for object: " + objectKey); throw new CompletionException(new IllegalStateException("SHA1 metadata missing for object: " + objectKey)); } GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() From 3427f9590fb847d9ab5f4c8a07fefe35796d6de7 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 06:36:52 +0800 Subject: [PATCH 21/62] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96=20S3=20?= =?UTF-8?q?=E6=89=98=E7=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/momirealms/craftengine/core/pack/host/impl/S3Host.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index 0a0c75aa9..28bb7ecf6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -79,6 +79,7 @@ public class S3Host implements ResourcePackHost { if (exception != null) { Throwable cause = exception.getCause(); if (cause instanceof NoSuchKeyException) { + CraftEngine.instance().logger().warn("[S3] Resource pack not found! Upload it first."); return Collections.emptyList(); } else { CraftEngine.instance().logger().warn( From a06a1b96ef4bf3e318f4db4b99105a7e391a7d9f Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 06:43:49 +0800 Subject: [PATCH 22/62] =?UTF-8?q?feat(core):=20=E4=B8=BA=20CustomApiHost?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E8=BA=AB=E4=BB=BD=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/impl/CustomApiHost.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java index e6eb10e4a..57aac027e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java @@ -23,11 +23,13 @@ import java.util.concurrent.CompletableFuture; public class CustomApiHost implements ResourcePackHost { public static final Factory FACTORY = new Factory(); - private final URL apiUrl; + private final String apiUrl; + private final String authKey; private final Path localFilePath; - public CustomApiHost(URL apiUrl, String localFilePath) { + public CustomApiHost(String apiUrl, String authKey, String localFilePath) { this.apiUrl = apiUrl; + this.authKey = authKey; this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); } @@ -38,6 +40,7 @@ public class CustomApiHost implements ResourcePackHost { try (HttpClient client = HttpClient.newHttpClient()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/v1/get-download-link?uuid=" + player)) + .header("Authorization", authKey) .GET() .build(); client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) @@ -69,6 +72,7 @@ public class CustomApiHost implements ResourcePackHost { try (HttpClient client = HttpClient.newHttpClient()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/v1/upload-resource-pack")) + .header("Authorization", authKey) .header("Content-Type", "application/octet-stream") .PUT(HttpRequest.BodyPublishers.ofFile(finalResourcePackPath)) .build(); @@ -114,15 +118,12 @@ public class CustomApiHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { String apiUrl = (String) arguments.get("api-url"); - String localFilePath = (String) arguments.get("local-file-path"); + String authKey = (String) arguments.get("auth-key"); if (apiUrl == null || apiUrl.isEmpty()) { throw new IllegalArgumentException("'api-url' cannot be empty for custom api host"); } - try { - return new CustomApiHost(URI.create(apiUrl).toURL(), localFilePath); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Invalid 'api-url' for custom api host: " + apiUrl, e); - } + String localFilePath = (String) arguments.get("local-file-path"); + return new CustomApiHost(apiUrl, authKey, localFilePath); } } } From e92e50bb78f8e17eacea19b908e34c854eed1e37 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 06:45:14 +0800 Subject: [PATCH 23/62] =?UTF-8?q?feat(core):=20=E4=B8=BA=20CustomApiHost?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E8=BA=AB=E4=BB=BD=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/core/pack/host/impl/CustomApiHost.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java index 57aac027e..7189970d3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java @@ -9,9 +9,7 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; import java.io.FileNotFoundException; -import java.net.MalformedURLException; import java.net.URI; -import java.net.URL; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -118,7 +116,7 @@ public class CustomApiHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { String apiUrl = (String) arguments.get("api-url"); - String authKey = (String) arguments.get("auth-key"); + String authKey = (String) arguments.getOrDefault("auth-key", ""); if (apiUrl == null || apiUrl.isEmpty()) { throw new IllegalArgumentException("'api-url' cannot be empty for custom api host"); } From 7b1f26df0ef479f90dcc139bc9c4d06a825d6f6b Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 09:06:41 +0800 Subject: [PATCH 24/62] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=20Alist?= =?UTF-8?q?=20=E6=89=98=E7=AE=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/pack/BukkitPackManager.java | 4 + .../plugin/network/PacketConsumers.java | 4 + .../core/pack/host/ResourcePackHosts.java | 2 + .../core/pack/host/impl/AlistHost.java | 279 ++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 74b5369bb..48cd579e6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.pack; import net.momirealms.craftengine.bukkit.api.event.AsyncResourcePackGenerateEvent; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.command.feature.ReloadCommand; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; @@ -153,6 +154,9 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { player.sendPackets(packets, true); } } + }).exceptionally(throwable -> { + CraftEngine.instance().logger().warn("Failed to send resource pack to player " + player.name(), throwable); + return null; }); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index 089c62248..522dd68cf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -2132,6 +2132,10 @@ public class PacketConsumers { user.nettyChannel().writeAndFlush(newPacket); user.addResourcePackUUID(data.uuid()); } + }).exceptionally(throwable -> { + CraftEngine.instance().logger().warn("Failed to handle ClientboundResourcePackPushPacket", throwable); + user.simulatePacket(FastNMS.INSTANCE.constructor$ServerboundResourcePackPacket$SUCCESSFULLY_LOADED(packUUID)); + return null; }); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundResourcePackPushPacket", e); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java index 8f54ecb7b..35e40c536 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -17,6 +17,7 @@ public class ResourcePackHosts { public static final Key LOBFILE = Key.of("craftengine:lobfile"); public static final Key S3_HOST = Key.of("craftengine:s3_host"); public static final Key CUSTOM_API_HOST = Key.of("craftengine:custom_api_host"); + public static final Key ALIST_HOST = Key.of("craftengine:alist_host"); static { register(NONE, NoneHost.FACTORY); @@ -25,6 +26,7 @@ public class ResourcePackHosts { register(LOBFILE, LobFileHost.FACTORY); register(S3_HOST, S3Host.FACTORY); register(CUSTOM_API_HOST, CustomApiHost.FACTORY); + register(ALIST_HOST, AlistHost.FACTORY); } public static void register(Key key, ResourcePackHostFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java new file mode 100644 index 000000000..8ddd15b6c --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -0,0 +1,279 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.Pair; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class AlistHost implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + private final String apiUrl; + private final String userName; + private final String password; + private final String filePassword; + private final String otpCode; + private final Duration jwtTokenExpiration; + private final String filePath; + private final Path localFilePath; + private Pair jwtToken; + private String cacheSha1; + + public AlistHost(String apiUrl, + String userName, + String password, + String filePassword, + String otpCode, + Duration jwtTokenExpiration, + String filePath, + String localFilePath) { + this.apiUrl = apiUrl; + this.userName = userName; + this.password = password; + this.filePassword = filePassword; + this.otpCode = otpCode; + this.jwtTokenExpiration = jwtTokenExpiration; + this.filePath = filePath; + this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); + this.readCacheFromDisk(); + } + + private void readCacheFromDisk() { + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("alist.cache"); + if (!Files.exists(cachePath)) return; + try (InputStream is = Files.newInputStream(cachePath)) { + cacheSha1 = new String(is.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + CraftEngine.instance().logger().warn("[Alist] Failed to read cache file", e); + } + } + + private void saveCacheToDisk() { + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("alist.cache"); + try { + Files.writeString(cachePath, cacheSha1); + } catch (IOException e) { + CraftEngine.instance().logger().warn("[Alist] Failed to write cache file", e); + } + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + CompletableFuture> future = new CompletableFuture<>(); + CraftEngine.instance().scheduler().executeAsync(() -> { + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(apiUrl + "/api/fs/get")) + .header("Authorization", getOrRefreshJwtToken()) + .header("Content-Type", "application/json") + .POST(getRequestResourcePackDownloadLinkPost()) + .build(); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> handleResourcePackDownloadLinkResponse(response, future)) + .exceptionally(ex -> { + CraftEngine.instance().logger().severe("[Alist] Failed to request resource pack download link", ex); + future.completeExceptionally(ex); + return null; + }); + } + }); + return future; + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + if (this.localFilePath != null) resourcePackPath = this.localFilePath; + Path finalResourcePackPath = resourcePackPath; + CraftEngine.instance().scheduler().executeAsync(() -> { + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(apiUrl + "/api/fs/put")) + .header("Authorization", getOrRefreshJwtToken()) + .header("File-Path", URLEncoder.encode(filePath, StandardCharsets.UTF_8) + .replace("/", "%2F")) + .header("overwrite", "true") + .header("password", filePassword) + .header("Content-Type", "application/x-zip-compressed") + .PUT(HttpRequest.BodyPublishers.ofFile(finalResourcePackPath)) + .build(); + long requestStart = System.currentTimeMillis(); + CraftEngine.instance().logger().info("[Alist] Starting file upload..."); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + long uploadTime = System.currentTimeMillis() - requestStart; + if (response.statusCode() == 200) { + cacheSha1 = calculateLocalFileSha1(finalResourcePackPath); + saveCacheToDisk(); + CraftEngine.instance().logger().info("[Alist] Upload resource pack success after " + uploadTime + "ms"); + future.complete(null); + } else { + future.completeExceptionally(new RuntimeException("Upload failed with status code: " + response.statusCode())); + } + }) + .exceptionally(ex -> { + long uploadTime = System.currentTimeMillis() - requestStart; + CraftEngine.instance().logger().severe( + "[Alist] Failed to upload resource pack after " + uploadTime + "ms", ex); + future.completeExceptionally(ex); + return null; + }); + } catch (IOException e) { + CraftEngine.instance().logger().warn("[Alist] Failed to upload resource pack: " + e.getMessage()); + future.completeExceptionally(e); + } + }); + return future; + } + + @Nullable + private String getOrRefreshJwtToken() { + if (jwtToken == null || jwtToken.right().before(new Date())) { + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(apiUrl + "/api/auth/login")) + .header("Content-Type", "application/json") + .POST(getLoginPost()) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + CraftEngine.instance().logger().warn("[Alist] Failed to get JWT token: " + response.body()); + return null; + } + JsonObject jsonData = parseJson(response.body()); + JsonElement code = jsonData.get("code"); + if (code.isJsonPrimitive() && code.getAsJsonPrimitive().isNumber() && code.getAsJsonPrimitive().getAsInt() == 200) { + JsonElement data = jsonData.get("data"); + if (data.isJsonObject()) { + JsonObject jsonObj = data.getAsJsonObject(); + jwtToken = Pair.of( + jsonObj.getAsJsonPrimitive("token").getAsString(), + new Date(System.currentTimeMillis() + jwtTokenExpiration.toMillis()) + ); + return jwtToken.left(); + } + CraftEngine.instance().logger().warn("[Alist] Failed to get JWT token: " + response.body()); + return null; + } + CraftEngine.instance().logger().warn("[Alist] Failed to get JWT token: " + response.body()); + return null; + } catch (IOException | InterruptedException e) { + CraftEngine.instance().logger().warn("[Alist] Failed to get JWT token", e); + return null; + } + } + return jwtToken.left(); + } + + private HttpRequest.BodyPublisher getLoginPost() { + String body = "{\"username\":\"" + userName + "\",\"password\":\"" + password + "\""; + if (otpCode != null && !otpCode.isEmpty()) { + body += ",\"otp_code\":\"" + otpCode + "\""; + } + body += "}"; + return HttpRequest.BodyPublishers.ofString(body); + } + + private HttpRequest.BodyPublisher getRequestResourcePackDownloadLinkPost() { + String body = "{\"path\":\"" + filePath + "\",\"password\":\"" + filePassword + "\"}"; + return HttpRequest.BodyPublishers.ofString(body); + } + + private void handleResourcePackDownloadLinkResponse( + HttpResponse response, CompletableFuture> future) { + if (response.statusCode() == 200) { + JsonObject json = parseJson(response.body()); + JsonElement code = json.get("code"); + if (code.isJsonPrimitive() && code.getAsJsonPrimitive().isNumber() && code.getAsJsonPrimitive().getAsInt() == 200) { + JsonElement data = json.get("data"); + if (data.isJsonObject()) { + JsonObject dataObj = data.getAsJsonObject(); + boolean isDir = dataObj.getAsJsonPrimitive("is_dir").getAsBoolean(); + if (!isDir) { + String url = dataObj.getAsJsonPrimitive("raw_url").getAsString(); + UUID uuid = UUID.nameUUIDFromBytes(cacheSha1.getBytes(StandardCharsets.UTF_8)); + future.complete(List.of(new ResourcePackDownloadData(url, uuid, cacheSha1))); + return; + } + } + } + } + future.completeExceptionally( + new RuntimeException("Failed to request resource pack download link: " + response.body())); + } + + private JsonObject parseJson(String json) { + try { + return GsonHelper.get().fromJson( + json, + JsonObject.class + ); + } catch (JsonSyntaxException e) { + throw new RuntimeException("Invalid JSON response: " + json, e); + } + } + + private String calculateLocalFileSha1(Path filePath) { + try (InputStream is = Files.newInputStream(filePath)) { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] buffer = new byte[8192]; + int len; + while ((len = is.read(buffer)) != -1) { + md.update(buffer, 0, len); + } + byte[] digest = md.digest(); + return HexFormat.of().formatHex(digest); + } catch (IOException | NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to calculate SHA1", e); + } + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + String apiUrl = (String) arguments.get("api-url"); + if (apiUrl == null || apiUrl.isEmpty()) { + throw new IllegalArgumentException("'api-url' cannot be empty for Alist host"); + } + String userName = (String) arguments.get("username"); + if (userName == null || userName.isEmpty()) { + throw new IllegalArgumentException("'username' cannot be empty for Alist host"); + } + String password = (String) arguments.get("password"); + if (password == null || password.isEmpty()) { + throw new IllegalArgumentException("'password' cannot be empty for Alist host"); + } + String filePassword = (String) arguments.getOrDefault("file-password", ""); + String otpCode = (String) arguments.get("otp-code"); + Duration jwtTokenExpiration = Duration.ofHours((int) arguments.getOrDefault("jwt-token-expiration", 48)); + String filePath = (String) arguments.get("file-path"); + if (filePath == null || filePath.isEmpty()) { + throw new IllegalArgumentException("'file-path' cannot be empty for Alist host"); + } + String localFilePath = (String) arguments.get("local-file-path"); + return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, localFilePath); + } + } +} From e8b56e45336e08ccb55d04c598de1af8b641a4dc Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 09:41:17 +0800 Subject: [PATCH 25/62] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=20Alist?= =?UTF-8?q?=20=E6=89=98=E7=AE=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/impl/AlistHost.java | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 8ddd15b6c..72d8d55ca 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -36,6 +36,7 @@ public class AlistHost implements ResourcePackHost { private final String otpCode; private final Duration jwtTokenExpiration; private final String filePath; + private final boolean disabledUpload; private final Path localFilePath; private Pair jwtToken; private String cacheSha1; @@ -47,6 +48,7 @@ public class AlistHost implements ResourcePackHost { String otpCode, Duration jwtTokenExpiration, String filePath, + boolean disabledUpload, String localFilePath) { this.apiUrl = apiUrl; this.userName = userName; @@ -55,6 +57,7 @@ public class AlistHost implements ResourcePackHost { this.otpCode = otpCode; this.jwtTokenExpiration = jwtTokenExpiration; this.filePath = filePath; + this.disabledUpload = disabledUpload; this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); this.readCacheFromDisk(); } @@ -103,6 +106,11 @@ public class AlistHost implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { + if (disabledUpload) { + cacheSha1 = ""; + saveCacheToDisk(); + return CompletableFuture.completedFuture(null); + } CompletableFuture future = new CompletableFuture<>(); if (this.localFilePath != null) resourcePackPath = this.localFilePath; Path finalResourcePackPath = resourcePackPath; @@ -212,7 +220,33 @@ public class AlistHost implements ResourcePackHost { boolean isDir = dataObj.getAsJsonPrimitive("is_dir").getAsBoolean(); if (!isDir) { String url = dataObj.getAsJsonPrimitive("raw_url").getAsString(); - UUID uuid = UUID.nameUUIDFromBytes(cacheSha1.getBytes(StandardCharsets.UTF_8)); + if ((cacheSha1 == null || cacheSha1.isEmpty()) && disabledUpload) { + try (HttpClient client = HttpClient.newHttpClient()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .GET() + .build(); + HttpResponse responseHash = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + try (InputStream inputStream = responseHash.body()) { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] buffer = new byte[8192]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + md.update(buffer, 0, len); + } + byte[] digest = md.digest(); + cacheSha1 = HexFormat.of().formatHex(digest); + saveCacheToDisk(); + } catch (NoSuchAlgorithmException e) { + future.completeExceptionally(new RuntimeException("Failed to get resource pack hash")); + return; + } + } catch (IOException | InterruptedException e) { + future.completeExceptionally(new RuntimeException("Failed to get resource pack hash")); + return; + } + } + UUID uuid = UUID.nameUUIDFromBytes(Objects.requireNonNull(cacheSha1).getBytes(StandardCharsets.UTF_8)); future.complete(List.of(new ResourcePackDownloadData(url, uuid, cacheSha1))); return; } @@ -272,8 +306,9 @@ public class AlistHost implements ResourcePackHost { if (filePath == null || filePath.isEmpty()) { throw new IllegalArgumentException("'file-path' cannot be empty for Alist host"); } + boolean disabledUpload = (boolean) arguments.getOrDefault("disabled-upload", false); String localFilePath = (String) arguments.get("local-file-path"); - return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, localFilePath); + return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, disabledUpload, localFilePath); } } } From edce537344043432ab509720d7376838810430ed Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 11:04:41 +0800 Subject: [PATCH 26/62] =?UTF-8?q?feat(core):=20=E4=B8=BA=20HTTP=20?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/build.gradle.kts | 1 + .../core/pack/host/impl/AlistHost.java | 23 ++++++++--- .../core/pack/host/impl/CustomApiHost.java | 17 +++++++-- .../core/pack/host/impl/LobFileHost.java | 17 +++++++-- .../core/pack/host/impl/S3Host.java | 20 ++++++++++ .../craftengine/core/util/MiscUtils.java | 38 +++++++++++++++++++ 6 files changed, 102 insertions(+), 14 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 42965a69a..0ba9d3b0a 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { compileOnly("org.ahocorasick:ahocorasick:${rootProject.properties["ahocorasick_version"]}") // Amazon S3 compileOnly("software.amazon.awssdk:s3:${rootProject.properties["amazon_awssdk_version"]}") + compileOnly("software.amazon.awssdk:netty-nio-client:${rootProject.properties["amazon_awssdk_version"]}") } java { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 72d8d55ca..baf050e18 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -8,11 +8,14 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.Pair; import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; +import java.net.Authenticator; +import java.net.ProxySelector; import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; @@ -38,6 +41,8 @@ public class AlistHost implements ResourcePackHost { private final String filePath; private final boolean disabledUpload; private final Path localFilePath; + private final ProxySelector proxy; + private final Authenticator auth; private Pair jwtToken; private String cacheSha1; @@ -49,7 +54,9 @@ public class AlistHost implements ResourcePackHost { Duration jwtTokenExpiration, String filePath, boolean disabledUpload, - String localFilePath) { + String localFilePath, + ProxySelector proxy, + Authenticator auth) { this.apiUrl = apiUrl; this.userName = userName; this.password = password; @@ -59,6 +66,8 @@ public class AlistHost implements ResourcePackHost { this.filePath = filePath; this.disabledUpload = disabledUpload; this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); + this.proxy = proxy; + this.auth = auth; this.readCacheFromDisk(); } @@ -85,7 +94,7 @@ public class AlistHost implements ResourcePackHost { public CompletableFuture> requestResourcePackDownloadLink(UUID player) { CompletableFuture> future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newHttpClient()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/fs/get")) .header("Authorization", getOrRefreshJwtToken()) @@ -115,7 +124,7 @@ public class AlistHost implements ResourcePackHost { if (this.localFilePath != null) resourcePackPath = this.localFilePath; Path finalResourcePackPath = resourcePackPath; CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newHttpClient()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/fs/put")) .header("Authorization", getOrRefreshJwtToken()) @@ -158,7 +167,7 @@ public class AlistHost implements ResourcePackHost { @Nullable private String getOrRefreshJwtToken() { if (jwtToken == null || jwtToken.right().before(new Date())) { - try (HttpClient client = HttpClient.newHttpClient()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/auth/login")) .header("Content-Type", "application/json") @@ -221,7 +230,7 @@ public class AlistHost implements ResourcePackHost { if (!isDir) { String url = dataObj.getAsJsonPrimitive("raw_url").getAsString(); if ((cacheSha1 == null || cacheSha1.isEmpty()) && disabledUpload) { - try (HttpClient client = HttpClient.newHttpClient()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() @@ -308,7 +317,9 @@ public class AlistHost implements ResourcePackHost { } boolean disabledUpload = (boolean) arguments.getOrDefault("disabled-upload", false); String localFilePath = (String) arguments.get("local-file-path"); - return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, disabledUpload, localFilePath); + ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); + Authenticator proxyAuth = MiscUtils.getAuthenticator(arguments.get("proxy")); + return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, disabledUpload, localFilePath, proxy, proxyAuth); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java index 7189970d3..86e753eb0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java @@ -7,8 +7,11 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.MiscUtils; import java.io.FileNotFoundException; +import java.net.Authenticator; +import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -24,18 +27,22 @@ public class CustomApiHost implements ResourcePackHost { private final String apiUrl; private final String authKey; private final Path localFilePath; + private final ProxySelector proxy; + private final Authenticator auth; - public CustomApiHost(String apiUrl, String authKey, String localFilePath) { + public CustomApiHost(String apiUrl, String authKey, String localFilePath, ProxySelector proxy, Authenticator auth) { this.apiUrl = apiUrl; this.authKey = authKey; this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); + this.proxy = proxy; + this.auth = auth; } @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { CompletableFuture> future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newHttpClient()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/v1/get-download-link?uuid=" + player)) .header("Authorization", authKey) @@ -67,7 +74,7 @@ public class CustomApiHost implements ResourcePackHost { if (this.localFilePath != null) resourcePackPath = this.localFilePath; Path finalResourcePackPath = resourcePackPath; CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newHttpClient()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/v1/upload-resource-pack")) .header("Authorization", authKey) @@ -121,7 +128,9 @@ public class CustomApiHost implements ResourcePackHost { throw new IllegalArgumentException("'api-url' cannot be empty for custom api host"); } String localFilePath = (String) arguments.get("local-file-path"); - return new CustomApiHost(apiUrl, authKey, localFilePath); + ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); + Authenticator proxyAuth = MiscUtils.getAuthenticator(arguments.get("proxy")); + return new CustomApiHost(apiUrl, authKey, localFilePath, proxy, proxyAuth); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 56a8136f0..25e1550cd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -7,10 +7,13 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.MiscUtils; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.Authenticator; +import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -28,15 +31,19 @@ public class LobFileHost implements ResourcePackHost { public static final Factory FACTORY = new Factory(); private final Path forcedPackPath; private final String apiKey; + private final ProxySelector proxy; + private final Authenticator auth; private AccountInfo accountInfo; private String url; private String sha1; private UUID uuid; - public LobFileHost(String localFile, String apiKey) { + public LobFileHost(String localFile, String apiKey, ProxySelector proxy, Authenticator auth) { this.forcedPackPath = localFile == null ? null : ResourcePackHost.customPackPath(localFile); this.apiKey = apiKey; + this.proxy = proxy; + this.auth = auth; this.readCacheFromDisk(); } @@ -114,7 +121,7 @@ public class LobFileHost implements ResourcePackHost { String sha1Hash = hashes.get("SHA-1"); String sha256Hash = hashes.get("SHA-256"); - try (HttpClient client = HttpClient.newHttpClient()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { String boundary = UUID.randomUUID().toString(); HttpRequest request = HttpRequest.newBuilder() @@ -154,7 +161,7 @@ public class LobFileHost implements ResourcePackHost { } public CompletableFuture fetchAccountInfo() { - try (HttpClient client = HttpClient.newHttpClient()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lobfile.com/api/v3/rest/get-account-info")) .header("X-API-Key", apiKey) @@ -276,7 +283,9 @@ public class LobFileHost implements ResourcePackHost { if (apiKey == null || apiKey.isEmpty()) { throw new RuntimeException("Missing 'api-key' for LobFileHost"); } - return new LobFileHost(localFilePath, apiKey); + ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); + Authenticator proxyAuth = MiscUtils.getAuthenticator(arguments.get("proxy")); + return new LobFileHost(localFilePath, apiKey, proxy, proxyAuth); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index 28bb7ecf6..4fbfe0441 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -10,6 +10,9 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.auth.signer.AwsS3V4Signer; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; +import software.amazon.awssdk.http.nio.netty.ProxyConfiguration; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; @@ -214,6 +217,23 @@ public class S3Host implements ResourcePackHost { ); } + Map proxySetting = MiscUtils.castToMap(arguments.get("proxy"), true); + if (proxySetting != null) { + String host = (String) proxySetting.get("host"); + int port = (Integer) proxySetting.get("port"); + String scheme = (String) proxySetting.get("scheme"); + String username = (String) proxySetting.get("username"); + String password = (String) proxySetting.get("password"); + if (host == null || host.isEmpty() || port <= 0 || port > 65535 || scheme == null || scheme.isEmpty()) { + throw new IllegalArgumentException("Invalid proxy setting"); + } + ProxyConfiguration.Builder builder = ProxyConfiguration.builder().host(host).port(port).scheme(scheme); + if (username != null) builder.username(username); + if (password != null) builder.password(password); + SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder().proxyConfiguration(builder.build()).build(); + s3AsyncClientBuilder.httpClient(httpClient); + } + S3AsyncClient s3AsyncClient = s3AsyncClientBuilder.build(); S3Presigner presigner = S3Presigner.builder() diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index 283afc968..2050b9853 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -4,6 +4,7 @@ import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; +import java.net.*; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -172,4 +173,41 @@ public class MiscUtils { throw new RuntimeException("Cannot convert " + o + " to Quaternionf"); } } + + public static ProxySelector getProxySelector(Object o) { + Map proxySetting = castToMap(o, true); + ProxySelector proxy = ProxySelector.getDefault(); + if (proxySetting != null) { + String proxyHost = (String) proxySetting.get("host"); + int proxyPort = (int) proxySetting.get("port"); + if (proxyHost == null || proxyHost.isEmpty() || proxyPort <= 0 || proxyPort > 65535) { + throw new IllegalArgumentException("Invalid proxy setting"); + } else { + proxy = ProxySelector.of(new InetSocketAddress(proxyHost, proxyPort)); + } + } + return proxy; + } + + public static Authenticator getAuthenticator(Object o) { + Map proxySetting = castToMap(o, true); + Authenticator auth = Authenticator.getDefault(); + if (proxySetting != null) { + String username = (String) proxySetting.get("username"); + String password = (String) proxySetting.get("password"); + auth = new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + if (getRequestorType() == RequestorType.PROXY) { + return new PasswordAuthentication( + username, + password.toCharArray() + ); + } + return null; + } + }; + } + return auth; + } } From df1c33238f78c0f1e6143f88609040c7e51b2543 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 13:35:58 +0800 Subject: [PATCH 27/62] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=20Dropbo?= =?UTF-8?q?x=20=E6=89=98=E7=AE=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/ResourcePackHosts.java | 2 + .../core/pack/host/impl/AlistHost.java | 17 +- .../core/pack/host/impl/CustomApiHost.java | 11 +- .../core/pack/host/impl/DropboxHost.java | 241 ++++++++++++++++++ .../core/pack/host/impl/LobFileHost.java | 11 +- .../core/pack/host/impl/S3Host.java | 14 +- .../craftengine/core/util/MiscUtils.java | 22 -- 7 files changed, 263 insertions(+), 55 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java index 35e40c536..057f42693 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -18,6 +18,7 @@ public class ResourcePackHosts { public static final Key S3_HOST = Key.of("craftengine:s3_host"); public static final Key CUSTOM_API_HOST = Key.of("craftengine:custom_api_host"); public static final Key ALIST_HOST = Key.of("craftengine:alist_host"); + public static final Key DROPBOX_HOST = Key.of("craftengine:dropbox_host"); static { register(NONE, NoneHost.FACTORY); @@ -27,6 +28,7 @@ public class ResourcePackHosts { register(S3_HOST, S3Host.FACTORY); register(CUSTOM_API_HOST, CustomApiHost.FACTORY); register(ALIST_HOST, AlistHost.FACTORY); + register(DROPBOX_HOST, DropboxHost.FACTORY); } public static void register(Key key, ResourcePackHostFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index baf050e18..03ff451c2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -14,7 +14,6 @@ import net.momirealms.craftengine.core.util.Pair; import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; -import java.net.Authenticator; import java.net.ProxySelector; import java.net.URI; import java.net.URLEncoder; @@ -42,7 +41,6 @@ public class AlistHost implements ResourcePackHost { private final boolean disabledUpload; private final Path localFilePath; private final ProxySelector proxy; - private final Authenticator auth; private Pair jwtToken; private String cacheSha1; @@ -55,8 +53,7 @@ public class AlistHost implements ResourcePackHost { String filePath, boolean disabledUpload, String localFilePath, - ProxySelector proxy, - Authenticator auth) { + ProxySelector proxy) { this.apiUrl = apiUrl; this.userName = userName; this.password = password; @@ -67,7 +64,6 @@ public class AlistHost implements ResourcePackHost { this.disabledUpload = disabledUpload; this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); this.proxy = proxy; - this.auth = auth; this.readCacheFromDisk(); } @@ -94,7 +90,7 @@ public class AlistHost implements ResourcePackHost { public CompletableFuture> requestResourcePackDownloadLink(UUID player) { CompletableFuture> future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/fs/get")) .header("Authorization", getOrRefreshJwtToken()) @@ -124,7 +120,7 @@ public class AlistHost implements ResourcePackHost { if (this.localFilePath != null) resourcePackPath = this.localFilePath; Path finalResourcePackPath = resourcePackPath; CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/fs/put")) .header("Authorization", getOrRefreshJwtToken()) @@ -167,7 +163,7 @@ public class AlistHost implements ResourcePackHost { @Nullable private String getOrRefreshJwtToken() { if (jwtToken == null || jwtToken.right().before(new Date())) { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/auth/login")) .header("Content-Type", "application/json") @@ -230,7 +226,7 @@ public class AlistHost implements ResourcePackHost { if (!isDir) { String url = dataObj.getAsJsonPrimitive("raw_url").getAsString(); if ((cacheSha1 == null || cacheSha1.isEmpty()) && disabledUpload) { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() @@ -318,8 +314,7 @@ public class AlistHost implements ResourcePackHost { boolean disabledUpload = (boolean) arguments.getOrDefault("disabled-upload", false); String localFilePath = (String) arguments.get("local-file-path"); ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - Authenticator proxyAuth = MiscUtils.getAuthenticator(arguments.get("proxy")); - return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, disabledUpload, localFilePath, proxy, proxyAuth); + return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, disabledUpload, localFilePath, proxy); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java index 86e753eb0..cc8b7b588 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java @@ -28,21 +28,19 @@ public class CustomApiHost implements ResourcePackHost { private final String authKey; private final Path localFilePath; private final ProxySelector proxy; - private final Authenticator auth; - public CustomApiHost(String apiUrl, String authKey, String localFilePath, ProxySelector proxy, Authenticator auth) { + public CustomApiHost(String apiUrl, String authKey, String localFilePath, ProxySelector proxy) { this.apiUrl = apiUrl; this.authKey = authKey; this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); this.proxy = proxy; - this.auth = auth; } @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { CompletableFuture> future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/v1/get-download-link?uuid=" + player)) .header("Authorization", authKey) @@ -74,7 +72,7 @@ public class CustomApiHost implements ResourcePackHost { if (this.localFilePath != null) resourcePackPath = this.localFilePath; Path finalResourcePackPath = resourcePackPath; CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(apiUrl + "/api/v1/upload-resource-pack")) .header("Authorization", authKey) @@ -129,8 +127,7 @@ public class CustomApiHost implements ResourcePackHost { } String localFilePath = (String) arguments.get("local-file-path"); ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - Authenticator proxyAuth = MiscUtils.getAuthenticator(arguments.get("proxy")); - return new CustomApiHost(apiUrl, authKey, localFilePath, proxy, proxyAuth); + return new CustomApiHost(apiUrl, authKey, localFilePath, proxy); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java new file mode 100644 index 000000000..8263a4aac --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -0,0 +1,241 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.MiscUtils; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.ProxySelector; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class DropboxHost implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + private final String accessToken; + private final String uploadPath; + private final ProxySelector proxy; + private final Path localFilePath; + + private String url; + private String sha1; + private UUID uuid; + + public DropboxHost(String accessToken, String uploadPath, ProxySelector proxy, String localFilePath) { + this.accessToken = accessToken; + this.uploadPath = uploadPath; + this.proxy = proxy; + this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); + readCacheFromDisk(); + } + + public void readCacheFromDisk() { + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("dropbox.cache"); + if (!Files.exists(cachePath)) return; + + try (InputStream is = Files.newInputStream(cachePath)) { + Map cache = GsonHelper.get().fromJson( + new InputStreamReader(is), + new TypeToken>(){}.getType() + ); + + this.url = cache.get("url"); + this.sha1 = cache.get("sha1"); + + String uuidString = cache.get("uuid"); + if (uuidString != null && !uuidString.isEmpty()) { + this.uuid = UUID.fromString(uuidString); + } + + CraftEngine.instance().logger().info("[DropBox] Loaded cached resource pack info"); + } catch (Exception e) { + CraftEngine.instance().logger().warn( + "[DropBox] Failed to read cache file: " + e.getMessage()); + } + } + + public void saveCacheToDisk() { + Map cache = new HashMap<>(); + cache.put("url", this.url); + cache.put("sha1", this.sha1); + cache.put("uuid", this.uuid != null ? this.uuid.toString() : ""); + + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("dropbox.cache"); + try { + Files.writeString( + cachePath, + GsonHelper.get().toJson(cache), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ); + } catch (IOException e) { + CraftEngine.instance().logger().warn( + "[DropBox] Failed to save cache: " + e.getMessage()); + } + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + if (url == null) return CompletableFuture.completedFuture(Collections.emptyList()); + return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(url, uuid, sha1))); + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + if (this.localFilePath != null) resourcePackPath = this.localFilePath; + + Path finalResourcePackPath = resourcePackPath; + + CraftEngine.instance().scheduler().executeAsync(() -> { + String sha1 = calculateLocalFileSha1(finalResourcePackPath); + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + JsonObject apiArg = new JsonObject(); + apiArg.addProperty("path", uploadPath); + apiArg.addProperty("mode", "overwrite"); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://content.dropboxapi.com/2/files/upload")) + .header("Authorization", "Bearer " + accessToken) + .header("Content-Type", "application/octet-stream") + .header("Dropbox-API-Arg", apiArg.toString()) + .POST(HttpRequest.BodyPublishers.ofFile(finalResourcePackPath)) + .build(); + + long uploadStart = System.currentTimeMillis(); + CraftEngine.instance().logger().info("[DropBox] Starting file upload..."); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + long uploadTime = System.currentTimeMillis() - uploadStart; + CraftEngine.instance().logger().info( + "[DropBox] Upload request completed in " + uploadTime + "ms"); + if (response.statusCode() == 200) { + this.sha1 = sha1; + this.uuid = UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)); + this.url = getDownloadUrl(); + saveCacheToDisk(); + future.complete(null); + return; + } + CraftEngine.instance().logger().warn( + "[DropBox] Upload resource pack failed: " + response.body()); + future.completeExceptionally(new RuntimeException(response.body())); + }) + .exceptionally(ex -> { + CraftEngine.instance().logger().warn("[DropBox] Upload resource pack failed", ex); + future.completeExceptionally(ex); + return null; + }); + } catch (FileNotFoundException e) { + CraftEngine.instance().logger().warn("[DropBox] Failed to upload resource pack: " + e.getMessage()); + future.completeExceptionally(e); + } + }); + + return future; + } + + private String getDownloadUrl() { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + try { + JsonObject requestJson = new JsonObject(); + requestJson.addProperty("path", uploadPath); + JsonObject settingsJson = new JsonObject(); + settingsJson.addProperty("requested_visibility", "public"); + requestJson.add("settings", settingsJson); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings")) + .header("Authorization", "Bearer " + accessToken) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(requestJson.toString())) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 409) { + JsonObject listJson = new JsonObject(); + listJson.addProperty("path", uploadPath); + HttpRequest listLinksRequest = HttpRequest.newBuilder() + .uri(URI.create("https://api.dropboxapi.com/2/sharing/list_shared_links")) + .header("Authorization", "Bearer " + accessToken) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(listJson.toString())) + .build(); + HttpResponse listResponse = client.send(listLinksRequest, HttpResponse.BodyHandlers.ofString()); + if (listResponse.statusCode() == 200) { + JsonObject responseJson = parseJson(listResponse.body()); + JsonArray links = responseJson.getAsJsonArray("links"); + if (!links.isEmpty()) { + return links.get(0).getAsJsonObject().get("url").getAsString().replace("dl=0", "dl=1"); + } + } + } else if (response.statusCode() != 200) { + CraftEngine.instance().logger().warn("[DropBox] Failed to get download url: " + response.body()); + return null; + } + JsonObject jsonData = parseJson(response.body()); + return jsonData.getAsJsonPrimitive("url").getAsString().replace("dl=0", "dl=1"); + } catch (IOException | InterruptedException e) { + CraftEngine.instance().logger().warn("[DropBox] Failed to get download url: " + e.getMessage()); + return null; + } + } + } + + private JsonObject parseJson(String json) { + try { + return GsonHelper.get().fromJson( + json, + JsonObject.class + ); + } catch (JsonSyntaxException e) { + throw new RuntimeException("Invalid JSON response: " + json, e); + } + } + + private String calculateLocalFileSha1(Path filePath) { + try (InputStream is = Files.newInputStream(filePath)) { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] buffer = new byte[8192]; + int len; + while ((len = is.read(buffer)) != -1) { + md.update(buffer, 0, len); + } + byte[] digest = md.digest(); + return HexFormat.of().formatHex(digest); + } catch (IOException | NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to calculate SHA1", e); + } + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + String localFilePath = (String) arguments.get("local-file-path"); + String accessToken = (String) arguments.get("access-token"); + if (accessToken == null || accessToken.isEmpty()) { + throw new RuntimeException("Missing 'access-token' for DropboxHost"); + } + String uploadPath = (String) arguments.getOrDefault("upload-path", "/resource_pack.zip"); + ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); + return new DropboxHost(accessToken, uploadPath, proxy, localFilePath); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 25e1550cd..637f73575 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -32,18 +32,16 @@ public class LobFileHost implements ResourcePackHost { private final Path forcedPackPath; private final String apiKey; private final ProxySelector proxy; - private final Authenticator auth; private AccountInfo accountInfo; private String url; private String sha1; private UUID uuid; - public LobFileHost(String localFile, String apiKey, ProxySelector proxy, Authenticator auth) { + public LobFileHost(String localFile, String apiKey, ProxySelector proxy) { this.forcedPackPath = localFile == null ? null : ResourcePackHost.customPackPath(localFile); this.apiKey = apiKey; this.proxy = proxy; - this.auth = auth; this.readCacheFromDisk(); } @@ -121,7 +119,7 @@ public class LobFileHost implements ResourcePackHost { String sha1Hash = hashes.get("SHA-1"); String sha256Hash = hashes.get("SHA-256"); - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { String boundary = UUID.randomUUID().toString(); HttpRequest request = HttpRequest.newBuilder() @@ -161,7 +159,7 @@ public class LobFileHost implements ResourcePackHost { } public CompletableFuture fetchAccountInfo() { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).authenticator(auth).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lobfile.com/api/v3/rest/get-account-info")) .header("X-API-Key", apiKey) @@ -284,8 +282,7 @@ public class LobFileHost implements ResourcePackHost { throw new RuntimeException("Missing 'api-key' for LobFileHost"); } ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - Authenticator proxyAuth = MiscUtils.getAuthenticator(arguments.get("proxy")); - return new LobFileHost(localFilePath, apiKey, proxy, proxyAuth); + return new LobFileHost(localFilePath, apiKey, proxy); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index 4fbfe0441..ec725211f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -42,7 +42,6 @@ public class S3Host implements ResourcePackHost { private final S3Presigner presigner; private final String bucket; private final String uploadPath; - private final String uploadFileName; private final String cdnDomain; private final String cdnProtocol; private final Duration validity; @@ -53,7 +52,6 @@ public class S3Host implements ResourcePackHost { S3Presigner presigner, String bucket, String uploadPath, - String uploadFileName, String cdnDomain, String cdnProtocol, Duration validity, @@ -63,7 +61,6 @@ public class S3Host implements ResourcePackHost { this.presigner = presigner; this.bucket = bucket; this.uploadPath = uploadPath; - this.uploadFileName = uploadFileName; this.cdnDomain = cdnDomain; this.cdnProtocol = cdnProtocol; this.validity = validity; @@ -72,7 +69,7 @@ public class S3Host implements ResourcePackHost { @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { - String objectKey = uploadPath + uploadFileName; + String objectKey = uploadPath; return s3AsyncClient.headObject(HeadObjectRequest.builder() .bucket(bucket) @@ -114,7 +111,7 @@ public class S3Host implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { if (this.localFilePath != null) resourcePackPath = this.localFilePath; - String objectKey = uploadPath + uploadFileName; + String objectKey = uploadPath; String sha1 = calculateLocalFileSha1(resourcePackPath); PutObjectRequest putObjectRequest = PutObjectRequest.builder() .bucket(bucket) @@ -189,8 +186,10 @@ public class S3Host implements ResourcePackHost { if (accessKeySecret == null || accessKeySecret.isEmpty()) { throw new IllegalArgumentException("'access-key-secret' cannot be empty for S3 host"); } - String uploadPath = (String) arguments.getOrDefault("upload-path", ""); - String uploadFileName = (String) arguments.getOrDefault("upload-file-name", "resource_pack.zip"); + String uploadPath = (String) arguments.getOrDefault("upload-path", "craftengine/resource_pack.zip"); + if (uploadPath == null || uploadPath.isEmpty()) { + throw new IllegalArgumentException("'upload-path' cannot be empty for S3 host"); + } String localFilePath = (String) arguments.get("local-file-path"); boolean useLegacySignature = (boolean) arguments.getOrDefault("use-legacy-signature", true); Duration validity = Duration.ofSeconds((int) arguments.getOrDefault("validity", 10)); @@ -247,7 +246,6 @@ public class S3Host implements ResourcePackHost { presigner, bucket, uploadPath, - uploadFileName, cdnDomain, cdnProtocol, validity, diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index 2050b9853..277bc4607 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -188,26 +188,4 @@ public class MiscUtils { } return proxy; } - - public static Authenticator getAuthenticator(Object o) { - Map proxySetting = castToMap(o, true); - Authenticator auth = Authenticator.getDefault(); - if (proxySetting != null) { - String username = (String) proxySetting.get("username"); - String password = (String) proxySetting.get("password"); - auth = new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - if (getRequestorType() == RequestorType.PROXY) { - return new PasswordAuthentication( - username, - password.toCharArray() - ); - } - return null; - } - }; - } - return auth; - } } From 01adeabdb08a780d571009bfe22e4749a8d9971d Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 15:24:34 +0800 Subject: [PATCH 28/62] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=20OneDri?= =?UTF-8?q?ve=20=E6=89=98=E7=AE=A1=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/ResourcePackHosts.java | 2 + .../core/pack/host/impl/OneDriveHost.java | 278 ++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java index 057f42693..34ad5a5a1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -19,6 +19,7 @@ public class ResourcePackHosts { public static final Key CUSTOM_API_HOST = Key.of("craftengine:custom_api_host"); public static final Key ALIST_HOST = Key.of("craftengine:alist_host"); public static final Key DROPBOX_HOST = Key.of("craftengine:dropbox_host"); + public static final Key ONEDRIVE_HOST = Key.of("craftengine:onedrive_host"); static { register(NONE, NoneHost.FACTORY); @@ -29,6 +30,7 @@ public class ResourcePackHosts { register(CUSTOM_API_HOST, CustomApiHost.FACTORY); register(ALIST_HOST, AlistHost.FACTORY); register(DROPBOX_HOST, DropboxHost.FACTORY); + register(ONEDRIVE_HOST, OneDriveHost.FACTORY); } public static void register(Key key, ResourcePackHostFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java new file mode 100644 index 000000000..a2686e209 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java @@ -0,0 +1,278 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.Tuple; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class OneDriveHost implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + private final String clientId; + private final String clientSecret; + private final ProxySelector proxy; + private final String filePath; + private final Path localFilePath; + private Tuple refreshToken; + private String sha1; + private String fileId; + + public OneDriveHost(String clientId, + String clientSecret, + String refreshToken, + String filePath, + String localFilePath, + ProxySelector proxy) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.proxy = proxy; + this.filePath = filePath; + this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); + this.refreshToken = Tuple.of(refreshToken, "", new Date()); + readCacheFromDisk(); + } + + public void readCacheFromDisk() { + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("onedrive.cache"); + if (!Files.exists(cachePath)) return; + + try (InputStream is = Files.newInputStream(cachePath)) { + Map cache = GsonHelper.get().fromJson( + new InputStreamReader(is), + new TypeToken>(){}.getType() + ); + + this.refreshToken = Tuple.of( + cache.get("refresh-token"), + cache.get("access-token"), + new Date(Long.parseLong(cache.get("refresh-token-expires-in")))); + this.sha1 = cache.get("sha1"); + this.fileId = cache.get("file-id"); + + CraftEngine.instance().logger().info("[OneDrive] Loaded cached resource pack info"); + } catch (Exception e) { + CraftEngine.instance().logger().warn( + "[OneDrive] Failed to read cache file: " + e.getMessage()); + } + } + + public void saveCacheToDisk() { + Map cache = new HashMap<>(); + cache.put("refresh-token", this.refreshToken.left()); + cache.put("access-token", this.refreshToken.mid()); + cache.put("refresh-token-expires-in", String.valueOf(this.refreshToken.right().getTime())); + cache.put("sha1", this.sha1); + cache.put("file-id", this.fileId); + + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("onedrive.cache"); + try { + Files.writeString( + cachePath, + GsonHelper.get().toJson(cache), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ); + } catch (IOException e) { + CraftEngine.instance().logger().warn( + "[OneDrive] Failed to save cache: " + e.getMessage()); + } + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + CompletableFuture> future = new CompletableFuture<>(); + CraftEngine.instance().scheduler().executeAsync(() -> { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + String accessToken = getOrRefreshJwtToken(); + saveCacheToDisk(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://graph.microsoft.com/v1.0/drive/items/" + fileId)) + .header("Authorization", "Bearer " + accessToken) + .header("Content-Type", "application/octet-stream") + .GET() + .build(); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + if (response.statusCode() != 200) { + CraftEngine.instance().logger().severe("[OneDrive] Failed to request resource pack download link: " + response.body()); + future.completeExceptionally(new IOException("Failed to request resource pack download link: " + response.body())); + return; + } + String downloadUrl = parseJson(response.body()).get("@microsoft.graph.downloadUrl").getAsString(); + future.complete(List.of(new ResourcePackDownloadData( + downloadUrl, + UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), + sha1 + ))); + }) + .exceptionally(ex -> { + CraftEngine.instance().logger().severe("[OneDrive] Failed to request resource pack download link", ex); + future.completeExceptionally(ex); + return null; + }); + } + }); + return future; + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + if (this.localFilePath != null) resourcePackPath = this.localFilePath; + Path finalResourcePackPath = resourcePackPath; + CraftEngine.instance().scheduler().executeAsync(() -> { + sha1 = calculateLocalFileSha1(finalResourcePackPath); + String accessToken = getOrRefreshJwtToken(); + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://graph.microsoft.com/v1.0/drive/root:/" + filePath + ":/content")) + .header("Authorization", "Bearer " + accessToken) + .header("Content-Type", "application/octet-stream") + .PUT(HttpRequest.BodyPublishers.ofFile(finalResourcePackPath)) + .build(); + long uploadStart = System.currentTimeMillis(); + CraftEngine.instance().logger().info("[OneDrive] Starting file upload..."); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + if (response.statusCode() == 200 || response.statusCode() == 201) { + CraftEngine.instance().logger().info("[OneDrive] Uploaded resource pack in " + (System.currentTimeMillis() - uploadStart) + "ms"); + fileId = parseJson(response.body()).get("id").getAsString(); + saveCacheToDisk(); + future.complete(null); + } else { + CraftEngine.instance().logger().warn("[OneDrive] Failed to upload resource pack: " + response.statusCode()); + future.completeExceptionally(new RuntimeException("Failed to upload resource pack: " + response.statusCode())); + } + }) + .exceptionally(ex -> { + CraftEngine.instance().logger().warn("[OneDrive] Failed to upload resource pack", ex); + future.completeExceptionally(ex); + return null; + }); + } catch (FileNotFoundException e) { + CraftEngine.instance().logger().warn("[OneDrive] File not found: " + e.getMessage()); + future.completeExceptionally(e); + } + }); + return future; + } + + private String getOrRefreshJwtToken() { + if (refreshToken == null || refreshToken.mid().isEmpty() || refreshToken.right().before(new Date())) { + try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + String formData = "client_id=" + URLEncoder.encode(clientId, StandardCharsets.UTF_8) + + "&client_secret=" + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) + + "&redirect_uri=" + URLEncoder.encode("https://alist.nn.ci/tool/onedrive/callback", StandardCharsets.UTF_8) + + "&refresh_token=" + URLEncoder.encode(refreshToken.left(), StandardCharsets.UTF_8) + + "&grant_type=refresh_token" + + "&scope=Files.ReadWrite.All+offline_access"; + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://login.microsoftonline.com/common/oauth2/v2.0/token")) + .header("Content-Type", "application/x-www-form-urlencoded") + .POST(HttpRequest.BodyPublishers.ofString(formData)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + CraftEngine.instance().logger().warn("[OneDrive] Failed to refresh JWT token: " + response.body()); + return refreshToken != null ? refreshToken.mid() : ""; + } + + JsonObject jsonData = parseJson(response.body()); + if (jsonData.has("error")) { + CraftEngine.instance().logger().warn("[OneDrive] Token refresh error: " + jsonData); + throw new RuntimeException("Token refresh failed: " + jsonData); + } + long expiresInMillis = jsonData.get("expires_in").getAsInt() * 1000L; + refreshToken = Tuple.of( + jsonData.get("refresh_token").getAsString(), + jsonData.get("access_token").getAsString(), + new Date(System.currentTimeMillis() + expiresInMillis - 10_000) + ); + } catch (IOException | InterruptedException e) { + CraftEngine.instance().logger().warn("[OneDrive] Token refresh failed: " + e.getMessage()); + throw new RuntimeException("Token refresh failed", e); + } + } + + return refreshToken.mid(); + } + + private JsonObject parseJson(String json) { + try { + return GsonHelper.get().fromJson( + json, + JsonObject.class + ); + } catch (JsonSyntaxException e) { + throw new RuntimeException("Invalid JSON response: " + json, e); + } + } + + private String calculateLocalFileSha1(Path filePath) { + try (InputStream is = Files.newInputStream(filePath)) { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] buffer = new byte[8192]; + int len; + while ((len = is.read(buffer)) != -1) { + md.update(buffer, 0, len); + } + byte[] digest = md.digest(); + return HexFormat.of().formatHex(digest); + } catch (IOException | NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to calculate SHA1", e); + } + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + String clientId = (String) arguments.get("client-id"); + if (clientId == null || clientId.isEmpty()) { + throw new RuntimeException("Missing 'client-id' for OneDriveHost"); + } + String clientSecret = (String) arguments.get("client-secret"); + if (clientSecret == null || clientSecret.isEmpty()) { + throw new RuntimeException("Missing 'client-secret' for OneDriveHost"); + } + String refreshToken = (String) arguments.get("refresh-token"); + if (refreshToken == null || refreshToken.isEmpty()) { + throw new RuntimeException("Missing 'refresh-token' for OneDriveHost"); + } + String filePath = (String) arguments.getOrDefault("file-path", "resource_pack.zip"); + if (filePath == null || filePath.isEmpty()) { + throw new RuntimeException("Missing 'file-path' for OneDriveHost"); + } + String localFilePath = (String) arguments.get("local-file-path"); + ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); + return new OneDriveHost(clientId, clientSecret, refreshToken, filePath, localFilePath, proxy); + } + } +} From 75cc88f10a275e7d6bcfa7fa6627729929db307f Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 15:32:20 +0800 Subject: [PATCH 29/62] =?UTF-8?q?refactor(pack):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E5=8C=85=E4=B8=BB=E6=9C=BA=E7=B1=BB=E7=9A=84?= =?UTF-8?q?=20JSON=20=E8=A7=A3=E6=9E=90=E5=92=8C=20SHA1=20=E8=AE=A1?= =?UTF-8?q?=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/impl/AlistHost.java | 34 ++--------------- .../core/pack/host/impl/CustomApiHost.java | 16 +------- .../core/pack/host/impl/DropboxHost.java | 36 ++---------------- .../core/pack/host/impl/LobFileHost.java | 15 +------- .../core/pack/host/impl/OneDriveHost.java | 38 +++---------------- .../core/pack/host/impl/S3Host.java | 28 +++----------- .../craftengine/core/util/GsonHelper.java | 23 +++++++++++ .../craftengine/core/util/HashUtils.java | 26 +++++++++++++ 8 files changed, 70 insertions(+), 146 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/HashUtils.java diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 03ff451c2..4bebf88ae 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -2,12 +2,12 @@ package net.momirealms.craftengine.core.pack.host.impl; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.HashUtils; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.Pair; @@ -137,7 +137,7 @@ public class AlistHost implements ResourcePackHost { .thenAccept(response -> { long uploadTime = System.currentTimeMillis() - requestStart; if (response.statusCode() == 200) { - cacheSha1 = calculateLocalFileSha1(finalResourcePackPath); + cacheSha1 = HashUtils.calculateLocalFileSha1(finalResourcePackPath); saveCacheToDisk(); CraftEngine.instance().logger().info("[Alist] Upload resource pack success after " + uploadTime + "ms"); future.complete(null); @@ -174,7 +174,7 @@ public class AlistHost implements ResourcePackHost { CraftEngine.instance().logger().warn("[Alist] Failed to get JWT token: " + response.body()); return null; } - JsonObject jsonData = parseJson(response.body()); + JsonObject jsonData = GsonHelper.parseJsonToJsonObject(response.body()); JsonElement code = jsonData.get("code"); if (code.isJsonPrimitive() && code.getAsJsonPrimitive().isNumber() && code.getAsJsonPrimitive().getAsInt() == 200) { JsonElement data = jsonData.get("data"); @@ -216,7 +216,7 @@ public class AlistHost implements ResourcePackHost { private void handleResourcePackDownloadLinkResponse( HttpResponse response, CompletableFuture> future) { if (response.statusCode() == 200) { - JsonObject json = parseJson(response.body()); + JsonObject json = GsonHelper.parseJsonToJsonObject(response.body()); JsonElement code = json.get("code"); if (code.isJsonPrimitive() && code.getAsJsonPrimitive().isNumber() && code.getAsJsonPrimitive().getAsInt() == 200) { JsonElement data = json.get("data"); @@ -262,32 +262,6 @@ public class AlistHost implements ResourcePackHost { new RuntimeException("Failed to request resource pack download link: " + response.body())); } - private JsonObject parseJson(String json) { - try { - return GsonHelper.get().fromJson( - json, - JsonObject.class - ); - } catch (JsonSyntaxException e) { - throw new RuntimeException("Invalid JSON response: " + json, e); - } - } - - private String calculateLocalFileSha1(Path filePath) { - try (InputStream is = Files.newInputStream(filePath)) { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] buffer = new byte[8192]; - int len; - while ((len = is.read(buffer)) != -1) { - md.update(buffer, 0, len); - } - byte[] digest = md.digest(); - return HexFormat.of().formatHex(digest); - } catch (IOException | NoSuchAlgorithmException e) { - throw new RuntimeException("Failed to calculate SHA1", e); - } - } - public static class Factory implements ResourcePackHostFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java index cc8b7b588..e5b45e71f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java @@ -1,7 +1,5 @@ package net.momirealms.craftengine.core.pack.host.impl; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; @@ -10,7 +8,6 @@ import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.MiscUtils; import java.io.FileNotFoundException; -import java.net.Authenticator; import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; @@ -49,7 +46,7 @@ public class CustomApiHost implements ResourcePackHost { client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenAccept(response -> { if (response.statusCode() == 200) { - Map jsonData = parseJson(response.body()); + Map jsonData = GsonHelper.parseJsonToMap(response.body()); String url = (String) jsonData.get("url"); String sha1 = (String) jsonData.get("sha1"); UUID uuid = UUID.fromString(sha1); @@ -105,17 +102,6 @@ public class CustomApiHost implements ResourcePackHost { return future; } - private Map parseJson(String json) { - try { - return GsonHelper.get().fromJson( - json, - new TypeToken>() {}.getType() - ); - } catch (JsonSyntaxException e) { - throw new RuntimeException("Invalid JSON response: " + json, e); - } - } - public static class Factory implements ResourcePackHostFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java index 8263a4aac..43784ad8f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -2,13 +2,13 @@ package net.momirealms.craftengine.core.pack.host.impl; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.HashUtils; import net.momirealms.craftengine.core.util.MiscUtils; import java.io.FileNotFoundException; @@ -24,8 +24,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -107,7 +105,7 @@ public class DropboxHost implements ResourcePackHost { Path finalResourcePackPath = resourcePackPath; CraftEngine.instance().scheduler().executeAsync(() -> { - String sha1 = calculateLocalFileSha1(finalResourcePackPath); + String sha1 = HashUtils.calculateLocalFileSha1(finalResourcePackPath); try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { JsonObject apiArg = new JsonObject(); apiArg.addProperty("path", uploadPath); @@ -179,7 +177,7 @@ public class DropboxHost implements ResourcePackHost { .build(); HttpResponse listResponse = client.send(listLinksRequest, HttpResponse.BodyHandlers.ofString()); if (listResponse.statusCode() == 200) { - JsonObject responseJson = parseJson(listResponse.body()); + JsonObject responseJson = GsonHelper.parseJsonToJsonObject(listResponse.body()); JsonArray links = responseJson.getAsJsonArray("links"); if (!links.isEmpty()) { return links.get(0).getAsJsonObject().get("url").getAsString().replace("dl=0", "dl=1"); @@ -189,7 +187,7 @@ public class DropboxHost implements ResourcePackHost { CraftEngine.instance().logger().warn("[DropBox] Failed to get download url: " + response.body()); return null; } - JsonObject jsonData = parseJson(response.body()); + JsonObject jsonData = GsonHelper.parseJsonToJsonObject(response.body()); return jsonData.getAsJsonPrimitive("url").getAsString().replace("dl=0", "dl=1"); } catch (IOException | InterruptedException e) { CraftEngine.instance().logger().warn("[DropBox] Failed to get download url: " + e.getMessage()); @@ -198,32 +196,6 @@ public class DropboxHost implements ResourcePackHost { } } - private JsonObject parseJson(String json) { - try { - return GsonHelper.get().fromJson( - json, - JsonObject.class - ); - } catch (JsonSyntaxException e) { - throw new RuntimeException("Invalid JSON response: " + json, e); - } - } - - private String calculateLocalFileSha1(Path filePath) { - try (InputStream is = Files.newInputStream(filePath)) { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] buffer = new byte[8192]; - int len; - while ((len = is.read(buffer)) != -1) { - md.update(buffer, 0, len); - } - byte[] digest = md.digest(); - return HexFormat.of().formatHex(digest); - } catch (IOException | NoSuchAlgorithmException e) { - throw new RuntimeException("Failed to calculate SHA1", e); - } - } - public static class Factory implements ResourcePackHostFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 637f73575..3b9c6d950 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.pack.host.impl; -import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; @@ -12,7 +11,6 @@ import net.momirealms.craftengine.core.util.MiscUtils; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.net.Authenticator; import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; @@ -225,7 +223,7 @@ public class LobFileHost implements ResourcePackHost { ) { try { if (response.statusCode() == 200) { - Map json = parseJson(response.body()); + Map json = GsonHelper.parseJsonToMap(response.body()); if (Boolean.TRUE.equals(json.get("success"))) { this.url = (String) json.get("url"); this.sha1 = localSha1; @@ -261,17 +259,6 @@ public class LobFileHost implements ResourcePackHost { return sb.toString(); } - private Map parseJson(String json) { - try { - return GsonHelper.get().fromJson( - json, - new TypeToken>() {}.getType() - ); - } catch (JsonSyntaxException e) { - throw new RuntimeException("Invalid JSON response: " + json, e); - } - } - public static class Factory implements ResourcePackHostFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java index a2686e209..5228bdde6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java @@ -1,13 +1,13 @@ package net.momirealms.craftengine.core.pack.host.impl; import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.HashUtils; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.Tuple; @@ -25,8 +25,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -122,7 +120,7 @@ public class OneDriveHost implements ResourcePackHost { future.completeExceptionally(new IOException("Failed to request resource pack download link: " + response.body())); return; } - String downloadUrl = parseJson(response.body()).get("@microsoft.graph.downloadUrl").getAsString(); + String downloadUrl = GsonHelper.parseJsonToJsonObject(response.body()).get("@microsoft.graph.downloadUrl").getAsString(); future.complete(List.of(new ResourcePackDownloadData( downloadUrl, UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), @@ -145,7 +143,7 @@ public class OneDriveHost implements ResourcePackHost { if (this.localFilePath != null) resourcePackPath = this.localFilePath; Path finalResourcePackPath = resourcePackPath; CraftEngine.instance().scheduler().executeAsync(() -> { - sha1 = calculateLocalFileSha1(finalResourcePackPath); + sha1 = HashUtils.calculateLocalFileSha1(finalResourcePackPath); String accessToken = getOrRefreshJwtToken(); try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() @@ -160,7 +158,7 @@ public class OneDriveHost implements ResourcePackHost { .thenAccept(response -> { if (response.statusCode() == 200 || response.statusCode() == 201) { CraftEngine.instance().logger().info("[OneDrive] Uploaded resource pack in " + (System.currentTimeMillis() - uploadStart) + "ms"); - fileId = parseJson(response.body()).get("id").getAsString(); + fileId = GsonHelper.parseJsonToJsonObject(response.body()).get("id").getAsString(); saveCacheToDisk(); future.complete(null); } else { @@ -204,7 +202,7 @@ public class OneDriveHost implements ResourcePackHost { return refreshToken != null ? refreshToken.mid() : ""; } - JsonObject jsonData = parseJson(response.body()); + JsonObject jsonData = GsonHelper.parseJsonToJsonObject(response.body()); if (jsonData.has("error")) { CraftEngine.instance().logger().warn("[OneDrive] Token refresh error: " + jsonData); throw new RuntimeException("Token refresh failed: " + jsonData); @@ -224,32 +222,6 @@ public class OneDriveHost implements ResourcePackHost { return refreshToken.mid(); } - private JsonObject parseJson(String json) { - try { - return GsonHelper.get().fromJson( - json, - JsonObject.class - ); - } catch (JsonSyntaxException e) { - throw new RuntimeException("Invalid JSON response: " + json, e); - } - } - - private String calculateLocalFileSha1(Path filePath) { - try (InputStream is = Files.newInputStream(filePath)) { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] buffer = new byte[8192]; - int len; - while ((len = is.read(buffer)) != -1) { - md.update(buffer, 0, len); - } - byte[] digest = md.digest(); - return HexFormat.of().formatHex(digest); - } catch (IOException | NoSuchAlgorithmException e) { - throw new RuntimeException("Failed to calculate SHA1", e); - } - } - public static class Factory implements ResourcePackHostFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index ec725211f..fc55dfb17 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.HashUtils; import net.momirealms.craftengine.core.util.MiscUtils; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; @@ -22,17 +23,15 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; -import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.time.Duration; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -112,7 +111,7 @@ public class S3Host implements ResourcePackHost { public CompletableFuture upload(Path resourcePackPath) { if (this.localFilePath != null) resourcePackPath = this.localFilePath; String objectKey = uploadPath; - String sha1 = calculateLocalFileSha1(resourcePackPath); + String sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); PutObjectRequest putObjectRequest = PutObjectRequest.builder() .bucket(bucket) .key(objectKey) @@ -147,21 +146,6 @@ public class S3Host implements ResourcePackHost { + (originalUrl.getQuery() != null ? "?" + originalUrl.getQuery() : ""); } - private String calculateLocalFileSha1(Path filePath) { - try (InputStream is = Files.newInputStream(filePath)) { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] buffer = new byte[8192]; - int len; - while ((len = is.read(buffer)) != -1) { - md.update(buffer, 0, len); - } - byte[] digest = md.digest(); - return HexFormat.of().formatHex(digest); - } catch (IOException | NoSuchAlgorithmException e) { - throw new RuntimeException("Failed to calculate SHA1", e); - } - } - public static class Factory implements ResourcePackHostFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java index f677c0ab8..7dcb40ce1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.util; import com.google.gson.*; +import com.google.gson.reflect.TypeToken; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -78,4 +79,26 @@ public class GsonHelper { } return merged; } + + public static JsonObject parseJsonToJsonObject(String json) { + try { + return get().fromJson( + json, + JsonObject.class + ); + } catch (JsonSyntaxException e) { + throw new RuntimeException("Invalid JSON response: " + json, e); + } + } + + public static Map parseJsonToMap(String json) { + try { + return GsonHelper.get().fromJson( + json, + new TypeToken>() {}.getType() + ); + } catch (JsonSyntaxException e) { + throw new RuntimeException("Invalid JSON response: " + json, e); + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/HashUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/HashUtils.java new file mode 100644 index 000000000..250adcfc7 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/HashUtils.java @@ -0,0 +1,26 @@ +package net.momirealms.craftengine.core.util; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; + +public class HashUtils { + public static String calculateLocalFileSha1(Path filePath) { + try (InputStream is = Files.newInputStream(filePath)) { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] buffer = new byte[8192]; + int len; + while ((len = is.read(buffer)) != -1) { + md.update(buffer, 0, len); + } + byte[] digest = md.digest(); + return HexFormat.of().formatHex(digest); + } catch (IOException | NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to calculate SHA1", e); + } + } +} From 867c994aae1018bc18b5d1dab9a297f485f7e0ba Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 17:20:07 +0800 Subject: [PATCH 30/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/craft-engine.properties | 27 --- .../craftengine/core/plugin/CraftEngine.java | 1 - .../core/plugin/dependency/Dependencies.java | 222 +++++++++++++++--- 3 files changed, 188 insertions(+), 62 deletions(-) diff --git a/bukkit/loader/src/main/resources/craft-engine.properties b/bukkit/loader/src/main/resources/craft-engine.properties index 826fd18e4..5901f43dd 100644 --- a/bukkit/loader/src/main/resources/craft-engine.properties +++ b/bukkit/loader/src/main/resources/craft-engine.properties @@ -32,31 +32,4 @@ lz4=${lz4_version} netty-codec-http2=${netty_version} reactive-streams=${reactive_streams_version} amazon-sdk-s3=${amazon_awssdk_version} -amazon-sdk-netty-nio-client=${amazon_awssdk_version} -amazon-sdk-core=${amazon_awssdk_version} -amazon-sdk-auth=${amazon_awssdk_version} -amazon-sdk-regions=${amazon_awssdk_version} -amazon-sdk-identity-spi=${amazon_awssdk_version} -amazon-sdk-http-client-spi=${amazon_awssdk_version} -amazon-sdk-protocol-core=${amazon_awssdk_version} -amazon-sdk-aws-xml-protocol=${amazon_awssdk_version} -amazon-sdk-json-utils=${amazon_awssdk_version} -amazon-sdk-aws-core=${amazon_awssdk_version} -amazon-sdk-utils=${amazon_awssdk_version} -amazon-sdk-annotations=${amazon_awssdk_version} -amazon-sdk-crt-core=${amazon_awssdk_version} -amazon-sdk-checksums=${amazon_awssdk_version} -amazon-sdk-profiles=${amazon_awssdk_version} -amazon-sdk-retries=${amazon_awssdk_version} -amazon-sdk-endpoints-spi=${amazon_awssdk_version} -amazon-sdk-arns=${amazon_awssdk_version} -amazon-sdk-aws-query-protocol=${amazon_awssdk_version} -amazon-sdk-http-auth-aws=${amazon_awssdk_version} -amazon-sdk-http-auth-spi=${amazon_awssdk_version} -amazon-sdk-http-auth=${amazon_awssdk_version} -amazon-sdk-http-auth-aws-eventstream=${amazon_awssdk_version} -amazon-sdk-checksums-spi=${amazon_awssdk_version} -amazon-sdk-retries-spi=${amazon_awssdk_version} -amazon-sdk-metrics-spi=${amazon_awssdk_version} -amazon-sdk-third-party-jackson-core=${amazon_awssdk_version} amazon-sdk-eventstream=${amazon_awssdk_eventstream_version} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 31cd4fca5..974e9c294 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -279,7 +279,6 @@ public abstract class CraftEngine implements Plugin { Dependencies.BSTATS_BASE, Dependencies.CAFFEINE, Dependencies.GEANTY_REF, - Dependencies.NETTY_HTTP, Dependencies.CLOUD_CORE, Dependencies.CLOUD_SERVICES, Dependencies.GSON, Dependencies.SLF4J_API, Dependencies.SLF4J_SIMPLE, diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 9699da54b..e9861fc2a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -14,6 +14,7 @@ public class Dependencies { "asm", Collections.emptyList() ); + public static final Dependency ASM_COMMONS = new Dependency( "asm-commons", "org.ow2.asm", @@ -21,6 +22,7 @@ public class Dependencies { "asm-commons", Collections.emptyList() ); + public static final Dependency JAR_RELOCATOR = new Dependency( "jar-relocator", "me.lucko", @@ -28,13 +30,7 @@ public class Dependencies { "jar-relocator", Collections.emptyList() ); - public static final Dependency NETTY_HTTP = new Dependency( - "netty-codec-http", - "io.netty", - "netty-codec-http", - "netty-codec-http", - Collections.emptyList() - ); + public static final Dependency GEANTY_REF = new Dependency( "geantyref", "io{}leangen{}geantyref", @@ -42,6 +38,7 @@ public class Dependencies { "geantyref", List.of(Relocation.of("geantyref", "io{}leangen{}geantyref")) ); + public static final Dependency CLOUD_CORE = new Dependency( "cloud-core", "org{}incendo", @@ -50,6 +47,7 @@ public class Dependencies { List.of(Relocation.of("cloud", "org{}incendo{}cloud"), Relocation.of("geantyref", "io{}leangen{}geantyref")) ); + public static final Dependency CLOUD_BRIGADIER = new Dependency( "cloud-brigadier", "org{}incendo", @@ -58,6 +56,7 @@ public class Dependencies { List.of(Relocation.of("cloud", "org{}incendo{}cloud"), Relocation.of("geantyref", "io{}leangen{}geantyref")) ); + public static final Dependency CLOUD_SERVICES = new Dependency( "cloud-services", "org{}incendo", @@ -66,6 +65,7 @@ public class Dependencies { List.of(Relocation.of("cloud", "org{}incendo{}cloud"), Relocation.of("geantyref", "io{}leangen{}geantyref")) ); + public static final Dependency CLOUD_BUKKIT = new Dependency( "cloud-bukkit", "org{}incendo", @@ -77,6 +77,7 @@ public class Dependencies { Relocation.of("examination", "net{}kyori{}examination"), Relocation.of("option", "net{}kyori{}option")) ); + public static final Dependency CLOUD_PAPER = new Dependency( "cloud-paper", "org{}incendo", @@ -88,6 +89,7 @@ public class Dependencies { Relocation.of("examination", "net{}kyori{}examination"), Relocation.of("option", "net{}kyori{}option")) ); + public static final Dependency CLOUD_MINECRAFT_EXTRAS = new Dependency( "cloud-minecraft-extras", "org{}incendo", @@ -99,6 +101,7 @@ public class Dependencies { Relocation.of("examination", "net{}kyori{}examination"), Relocation.of("option", "net{}kyori{}option")) ); + public static final Dependency BOOSTED_YAML = new Dependency( "boosted-yaml", "dev{}dejvokep", @@ -106,6 +109,7 @@ public class Dependencies { "boosted-yaml", List.of(Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml")) ); + public static final Dependency BSTATS_BASE = new Dependency( "bstats-base", "org{}bstats", @@ -113,6 +117,7 @@ public class Dependencies { "bstats-base", List.of(Relocation.of("bstats", "org{}bstats")) ); + public static final Dependency BSTATS_BUKKIT = new Dependency( "bstats-bukkit", "org{}bstats", @@ -125,6 +130,7 @@ public class Dependencies { return Dependencies.BSTATS_BASE.getVersion(); } }; + public static final Dependency GSON = new Dependency( "gson", "com.google.code.gson", @@ -132,6 +138,7 @@ public class Dependencies { "gson", Collections.emptyList() ); + public static final Dependency CAFFEINE = new Dependency( "caffeine", "com{}github{}ben-manes{}caffeine", @@ -139,6 +146,7 @@ public class Dependencies { "caffeine", List.of(Relocation.of("caffeine", "com{}github{}benmanes{}caffeine")) ); + public static final Dependency ZSTD = new Dependency( "zstd-jni", "com.github.luben", @@ -146,6 +154,7 @@ public class Dependencies { "zstd-jni", Collections.emptyList() ); + public static final Dependency SLF4J_API = new Dependency( "slf4j-api", "org.slf4j", @@ -153,6 +162,7 @@ public class Dependencies { "slf4j-api", Collections.emptyList() ); + public static final Dependency SLF4J_SIMPLE = new Dependency( "slf4j-simple", "org.slf4j", @@ -165,6 +175,7 @@ public class Dependencies { return Dependencies.SLF4J_API.getVersion(); } }; + public static final Dependency COMMONS_IO = new Dependency( "commons-io", "commons-io", @@ -172,6 +183,7 @@ public class Dependencies { "commons-io", List.of(Relocation.of("commons", "org{}apache{}commons")) ); + public static final Dependency BYTE_BUDDY = new Dependency( "byte-buddy", "net{}bytebuddy", @@ -179,6 +191,7 @@ public class Dependencies { "byte-buddy", List.of(Relocation.of("bytebuddy", "net{}bytebuddy")) ); + public static final Dependency SNAKE_YAML = new Dependency( "snake-yaml", "org{}yaml", @@ -186,6 +199,7 @@ public class Dependencies { "snakeyaml", List.of(Relocation.of("snakeyaml", "org{}yaml{}snakeyaml")) ); + public static final Dependency MINIMESSAGE = new Dependency( "adventure-text-minimessage", "net{}kyori", @@ -193,6 +207,7 @@ public class Dependencies { "adventure-text-minimessage", List.of(Relocation.of("adventure", "net{}kyori{}adventure")) ); + public static final Dependency TEXT_SERIALIZER_GSON = new Dependency( "adventure-text-serializer-gson", "net{}kyori", @@ -200,6 +215,7 @@ public class Dependencies { "adventure-text-serializer-gson", List.of(Relocation.of("adventure", "net{}kyori{}adventure")) ); + public static final Dependency TEXT_SERIALIZER_GSON_LEGACY = new Dependency( "adventure-text-serializer-json-legacy-impl", "net{}kyori", @@ -207,6 +223,7 @@ public class Dependencies { "adventure-text-serializer-json-legacy-impl", List.of(Relocation.of("adventure", "net{}kyori{}adventure")) ); + public static final Dependency TEXT_SERIALIZER_JSON = new Dependency( "adventure-text-serializer-json", "net{}kyori", @@ -214,6 +231,7 @@ public class Dependencies { "adventure-text-serializer-json", List.of(Relocation.of("adventure", "net{}kyori{}adventure")) ); + public static final Dependency AHO_CORASICK = new Dependency( "ahocorasick", "org{}ahocorasick", @@ -221,6 +239,7 @@ public class Dependencies { "aho-corasick", List.of(Relocation.of("ahocorasick", "org{}ahocorasick")) ); + public static final Dependency LZ4 = new Dependency( "lz4", "org{}lz4", @@ -265,7 +284,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_SDK_CORE = new Dependency( "amazon-sdk-core", @@ -276,7 +300,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_AUTH = new Dependency( "amazon-sdk-auth", @@ -287,7 +316,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_REGIONS = new Dependency( "amazon-sdk-regions", @@ -298,7 +332,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_IDENTITY_SPI = new Dependency( "amazon-sdk-identity-spi", @@ -309,7 +348,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_HTTP_CLIENT_SPI = new Dependency( "amazon-sdk-http-client-spi", @@ -320,7 +364,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_PROTOCOL_CORE = new Dependency( "amazon-sdk-protocol-core", @@ -331,7 +380,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_AWS_XML_PROTOCOL = new Dependency( "amazon-sdk-aws-xml-protocol", @@ -342,7 +396,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_JSON_UTILS = new Dependency( "amazon-sdk-json-utils", @@ -353,7 +412,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_AWS_CORE = new Dependency( "amazon-sdk-aws-core", @@ -364,7 +428,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_UTILS = new Dependency( "amazon-sdk-utils", @@ -375,7 +444,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_ANNOTATIONS = new Dependency( "amazon-sdk-annotations", @@ -386,7 +460,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_CRT_CORE = new Dependency( "amazon-sdk-crt-core", @@ -397,7 +476,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_CHECKSUMS = new Dependency( "amazon-sdk-checksums", @@ -408,7 +492,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_EVENTSTREAM = new Dependency( "amazon-sdk-eventstream", @@ -430,7 +519,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_RETRIES = new Dependency( "amazon-sdk-retries", @@ -441,7 +535,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_ENDPOINTS_SPI = new Dependency( "amazon-sdk-endpoints-spi", @@ -452,7 +551,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_ARNS = new Dependency( "amazon-sdk-arns", @@ -463,7 +567,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_AWS_QUERY_PROTOCOL = new Dependency( "amazon-sdk-aws-query-protocol", @@ -474,7 +583,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_HTTP_AUTH_AWS = new Dependency( "amazon-sdk-http-auth-aws", @@ -485,7 +599,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_HTTP_AUTH_SPI = new Dependency( "amazon-sdk-http-auth-spi", @@ -496,7 +615,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_HTTP_AUTH = new Dependency( "amazon-sdk-http-auth", @@ -507,7 +631,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_HTTP_AUTH_AWS_EVENTSTREAM = new Dependency( "amazon-sdk-http-auth-aws-eventstream", @@ -518,7 +647,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_CHECKSUMS_SPI = new Dependency( "amazon-sdk-checksums-spi", @@ -529,7 +663,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_RETRIES_SPI = new Dependency( "amazon-sdk-retries-spi", @@ -540,7 +679,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_METRICS_SPI = new Dependency( "amazon-sdk-metrics-spi", @@ -551,7 +695,12 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; public static final Dependency AMAZON_AWSSDK_THIRD_PARTY_JACKSON_CORE = new Dependency( "amazon-sdk-third-party-jackson-core", @@ -562,5 +711,10 @@ public class Dependencies { Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") ) - ); + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; } \ No newline at end of file From ef36d28381ac56e7b8fcca6b81acb8c4d12c93ca Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 18:15:20 +0800 Subject: [PATCH 31/62] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/src/main/resources/config.yml | 14 ++- .../item/recipe/BukkitRecipeManager.java | 2 +- .../item/recipe/RecipeEventListener.java | 2 +- .../bukkit/pack/BukkitPackManager.java | 6 +- .../core/pack/AbstractPackManager.java | 2 +- .../core/pack/host/ResourcePackHost.java | 6 - .../core/pack/host/ResourcePackHosts.java | 26 ++-- .../core/pack/host/impl/AlistHost.java | 14 +-- .../core/pack/host/impl/CustomApiHost.java | 119 ------------------ .../core/pack/host/impl/DropboxHost.java | 25 ++-- .../core/pack/host/impl/LobFileHost.java | 15 +-- .../core/pack/host/impl/OneDriveHost.java | 12 +- .../core/pack/host/impl/S3Host.java | 59 ++++----- .../core/pack/host/impl/SelfHost.java | 6 +- .../pack/host/impl/SelfHostHttpServer.java | 6 - .../core/plugin/config/Config.java | 39 ++++-- .../gui/category/ItemBrowserManagerImpl.java | 2 +- .../craftengine/core/util/MiscUtils.java | 3 +- 18 files changed, 102 insertions(+), 256 deletions(-) delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index 884d642cc..d4eb04482 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -65,24 +65,30 @@ resource-pack: - CustomNameplates/ResourcePack - BetterModel/build - BetterHud/build - send: + delivery: + # Send the resource pack on joining the server send-on-join: true - send-on-reload: true kick-if-declined: true prompt: "To fully experience our server, please accept our custom resource pack." # If you are hosting the resource pack by yourself, replace `localhost` with your server ip otherwise it would only work on your local pc # If using BungeeCord or Velocity, consider using a proxy-side plugin to handle resource pack delivery. # Read this page for more host types: https://mo-mi.gitbook.io/xiaomomi-plugins/craftengine/plugin-wiki/craftengine/resource-pack/host - host: + hosting: - type: "self_host" ip: "localhost" port: 8163 protocol: "http" deny-non-minecraft-request: true - local-file-path: "./generated/resource_pack.zip" rate-limit: max-requests: 3 reset-interval: 20 + # Upload the resource pack automatically on generation + # When disabled, you must manually trigger uploads using the /ce upload command + auto-upload: true + # The file to upload + file-to-upload: "./generated/resource_pack.zip" + # Resend the resource pack to players upon successful upload + resend-on-upload: true duplicated-files-handler: - term: type: any_of diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java index 663ec5371..562f795a2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java @@ -14,8 +14,8 @@ import net.momirealms.craftengine.bukkit.util.RecipeUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.ItemBuildContext; -import net.momirealms.craftengine.core.item.recipe.Recipe; import net.momirealms.craftengine.core.item.recipe.*; +import net.momirealms.craftengine.core.item.recipe.Recipe; import net.momirealms.craftengine.core.item.recipe.vanilla.*; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index 5d4bd2632..918f68fec 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -12,8 +12,8 @@ import net.momirealms.craftengine.bukkit.util.ItemUtils; import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.item.*; -import net.momirealms.craftengine.core.item.recipe.Recipe; import net.momirealms.craftengine.core.item.recipe.*; +import net.momirealms.craftengine.core.item.recipe.Recipe; import net.momirealms.craftengine.core.item.recipe.input.CraftingInput; import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput; import net.momirealms.craftengine.core.item.recipe.input.SmithingInput; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 48cd579e6..d0134f042 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.bukkit.pack; import net.momirealms.craftengine.bukkit.api.event.AsyncResourcePackGenerateEvent; -import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.command.feature.ReloadCommand; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; @@ -121,12 +120,13 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { @EventHandler(priority = EventPriority.MONITOR) public void onAsyncResourcePackGenerate(AsyncResourcePackGenerateEvent event) { - resourcePackHost().upload(event.zipFilePath()).whenComplete((d, e) -> { + if (!Config.autoUpload()) return; + resourcePackHost().upload(Config.fileToUpload()).whenComplete((d, e) -> { if (e != null) { CraftEngine.instance().logger().warn("Failed to upload resource pack", e); return; } - if (!Config.sendPackOnReload()) return; + if (!Config.sendPackOnUpload()) return; for (BukkitServerPlayer player : this.plugin.networkManager().onlineUsers()) { sendResourcePack(player); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index d47cea0d8..d63079b13 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -147,7 +147,7 @@ public abstract class AbstractPackManager implements PackManager { @Override public void load() { - List> list = Config.instance().settings().getMapList("resource-pack.send.host"); + List> list = Config.instance().settings().getMapList("resource-pack.delivery.hosting"); if (list == null || list.isEmpty()) { this.resourcePackHost = NoneHost.INSTANCE; } else { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java index ced5a92c6..1da990f1d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java @@ -1,7 +1,5 @@ package net.momirealms.craftengine.core.pack.host; -import net.momirealms.craftengine.core.plugin.CraftEngine; - import java.nio.file.Path; import java.util.List; import java.util.UUID; @@ -12,8 +10,4 @@ public interface ResourcePackHost { CompletableFuture> requestResourcePackDownloadLink(UUID player); CompletableFuture upload(Path resourcePackPath); - - static Path customPackPath(String path) { - return path.startsWith(".") ? CraftEngine.instance().dataFolderPath().resolve(path) : Path.of(path); - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java index 34ad5a5a1..c6207c254 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -12,25 +12,23 @@ import java.util.Map; public class ResourcePackHosts { public static final Key NONE = Key.of("craftengine:none"); - public static final Key SELF_HOST = Key.of("craftengine:self_host"); - public static final Key EXTERNAL_HOST = Key.of("craftengine:external_host"); + public static final Key SELF = Key.of("craftengine:self"); + public static final Key EXTERNAL = Key.of("craftengine:external"); public static final Key LOBFILE = Key.of("craftengine:lobfile"); - public static final Key S3_HOST = Key.of("craftengine:s3_host"); - public static final Key CUSTOM_API_HOST = Key.of("craftengine:custom_api_host"); - public static final Key ALIST_HOST = Key.of("craftengine:alist_host"); - public static final Key DROPBOX_HOST = Key.of("craftengine:dropbox_host"); - public static final Key ONEDRIVE_HOST = Key.of("craftengine:onedrive_host"); + public static final Key S3 = Key.of("craftengine:s3"); + public static final Key ALIST = Key.of("craftengine:alist"); + public static final Key DROPBOX = Key.of("craftengine:dropbox"); + public static final Key ONEDRIVE = Key.of("craftengine:onedrive"); static { register(NONE, NoneHost.FACTORY); - register(SELF_HOST, SelfHost.FACTORY); - register(EXTERNAL_HOST, ExternalHost.FACTORY); + register(SELF, SelfHost.FACTORY); + register(EXTERNAL, ExternalHost.FACTORY); register(LOBFILE, LobFileHost.FACTORY); - register(S3_HOST, S3Host.FACTORY); - register(CUSTOM_API_HOST, CustomApiHost.FACTORY); - register(ALIST_HOST, AlistHost.FACTORY); - register(DROPBOX_HOST, DropboxHost.FACTORY); - register(ONEDRIVE_HOST, OneDriveHost.FACTORY); + register(S3, S3Host.FACTORY); + register(ALIST, AlistHost.FACTORY); + register(DROPBOX, DropboxHost.FACTORY); + register(ONEDRIVE, OneDriveHost.FACTORY); } public static void register(Key key, ResourcePackHostFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 4bebf88ae..1f27a3bb5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -39,7 +39,6 @@ public class AlistHost implements ResourcePackHost { private final Duration jwtTokenExpiration; private final String filePath; private final boolean disabledUpload; - private final Path localFilePath; private final ProxySelector proxy; private Pair jwtToken; private String cacheSha1; @@ -52,7 +51,6 @@ public class AlistHost implements ResourcePackHost { Duration jwtTokenExpiration, String filePath, boolean disabledUpload, - String localFilePath, ProxySelector proxy) { this.apiUrl = apiUrl; this.userName = userName; @@ -62,7 +60,6 @@ public class AlistHost implements ResourcePackHost { this.jwtTokenExpiration = jwtTokenExpiration; this.filePath = filePath; this.disabledUpload = disabledUpload; - this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); this.proxy = proxy; this.readCacheFromDisk(); } @@ -117,8 +114,6 @@ public class AlistHost implements ResourcePackHost { return CompletableFuture.completedFuture(null); } CompletableFuture future = new CompletableFuture<>(); - if (this.localFilePath != null) resourcePackPath = this.localFilePath; - Path finalResourcePackPath = resourcePackPath; CraftEngine.instance().scheduler().executeAsync(() -> { try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() @@ -129,7 +124,7 @@ public class AlistHost implements ResourcePackHost { .header("overwrite", "true") .header("password", filePassword) .header("Content-Type", "application/x-zip-compressed") - .PUT(HttpRequest.BodyPublishers.ofFile(finalResourcePackPath)) + .PUT(HttpRequest.BodyPublishers.ofFile(resourcePackPath)) .build(); long requestStart = System.currentTimeMillis(); CraftEngine.instance().logger().info("[Alist] Starting file upload..."); @@ -137,9 +132,9 @@ public class AlistHost implements ResourcePackHost { .thenAccept(response -> { long uploadTime = System.currentTimeMillis() - requestStart; if (response.statusCode() == 200) { - cacheSha1 = HashUtils.calculateLocalFileSha1(finalResourcePackPath); + cacheSha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); saveCacheToDisk(); - CraftEngine.instance().logger().info("[Alist] Upload resource pack success after " + uploadTime + "ms"); + CraftEngine.instance().logger().info("[Alist] Upload resource pack successfully in " + uploadTime + "ms"); future.complete(null); } else { future.completeExceptionally(new RuntimeException("Upload failed with status code: " + response.statusCode())); @@ -286,9 +281,8 @@ public class AlistHost implements ResourcePackHost { throw new IllegalArgumentException("'file-path' cannot be empty for Alist host"); } boolean disabledUpload = (boolean) arguments.getOrDefault("disabled-upload", false); - String localFilePath = (String) arguments.get("local-file-path"); ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, disabledUpload, localFilePath, proxy); + return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, disabledUpload, proxy); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java deleted file mode 100644 index e5b45e71f..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/CustomApiHost.java +++ /dev/null @@ -1,119 +0,0 @@ -package net.momirealms.craftengine.core.pack.host.impl; - -import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; -import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; -import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.GsonHelper; -import net.momirealms.craftengine.core.util.MiscUtils; - -import java.io.FileNotFoundException; -import java.net.ProxySelector; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -public class CustomApiHost implements ResourcePackHost { - public static final Factory FACTORY = new Factory(); - private final String apiUrl; - private final String authKey; - private final Path localFilePath; - private final ProxySelector proxy; - - public CustomApiHost(String apiUrl, String authKey, String localFilePath, ProxySelector proxy) { - this.apiUrl = apiUrl; - this.authKey = authKey; - this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); - this.proxy = proxy; - } - - @Override - public CompletableFuture> requestResourcePackDownloadLink(UUID player) { - CompletableFuture> future = new CompletableFuture<>(); - CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(apiUrl + "/api/v1/get-download-link?uuid=" + player)) - .header("Authorization", authKey) - .GET() - .build(); - client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenAccept(response -> { - if (response.statusCode() == 200) { - Map jsonData = GsonHelper.parseJsonToMap(response.body()); - String url = (String) jsonData.get("url"); - String sha1 = (String) jsonData.get("sha1"); - UUID uuid = UUID.fromString(sha1); - future.complete(List.of(new ResourcePackDownloadData(url, uuid, sha1))); - } - }) - .exceptionally(ex -> { - CraftEngine.instance().logger().warn("[CustomApi] Get resource pack download link failed", ex); - future.completeExceptionally(ex); - return null; - }); - } - }); - return future; - } - - @Override - public CompletableFuture upload(Path resourcePackPath) { - CompletableFuture future = new CompletableFuture<>(); - if (this.localFilePath != null) resourcePackPath = this.localFilePath; - Path finalResourcePackPath = resourcePackPath; - CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(apiUrl + "/api/v1/upload-resource-pack")) - .header("Authorization", authKey) - .header("Content-Type", "application/octet-stream") - .PUT(HttpRequest.BodyPublishers.ofFile(finalResourcePackPath)) - .build(); - long uploadStart = System.currentTimeMillis(); - CraftEngine.instance().logger().info("[CustomApi] Starting file upload..."); - client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenAccept(response -> { - long uploadTime = System.currentTimeMillis() - uploadStart; - CraftEngine.instance().logger().info( - "[CustomApi] Upload request completed in " + uploadTime + "ms"); - if (response.statusCode() == 200) { - future.complete(null); - } else { - future.completeExceptionally(new RuntimeException("Upload failed with status code: " + response.statusCode())); - } - }) - .exceptionally(ex -> { - CraftEngine.instance().logger().warn("[CustomApi] Upload resource pack failed", ex); - future.completeExceptionally(ex); - return null; - }); - } catch (FileNotFoundException e) { - CraftEngine.instance().logger().warn("[CustomApi] Resource pack not found: " + finalResourcePackPath); - future.completeExceptionally(e); - } - }); - return future; - } - - public static class Factory implements ResourcePackHostFactory { - - @Override - public ResourcePackHost create(Map arguments) { - String apiUrl = (String) arguments.get("api-url"); - String authKey = (String) arguments.getOrDefault("auth-key", ""); - if (apiUrl == null || apiUrl.isEmpty()) { - throw new IllegalArgumentException("'api-url' cannot be empty for custom api host"); - } - String localFilePath = (String) arguments.get("local-file-path"); - ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new CustomApiHost(apiUrl, authKey, localFilePath, proxy); - } - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java index 43784ad8f..1d9252667 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -32,17 +32,15 @@ public class DropboxHost implements ResourcePackHost { private final String accessToken; private final String uploadPath; private final ProxySelector proxy; - private final Path localFilePath; private String url; private String sha1; private UUID uuid; - public DropboxHost(String accessToken, String uploadPath, ProxySelector proxy, String localFilePath) { + public DropboxHost(String accessToken, String uploadPath, ProxySelector proxy) { this.accessToken = accessToken; this.uploadPath = uploadPath; this.proxy = proxy; - this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); readCacheFromDisk(); } @@ -100,12 +98,8 @@ public class DropboxHost implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { CompletableFuture future = new CompletableFuture<>(); - if (this.localFilePath != null) resourcePackPath = this.localFilePath; - - Path finalResourcePackPath = resourcePackPath; - CraftEngine.instance().scheduler().executeAsync(() -> { - String sha1 = HashUtils.calculateLocalFileSha1(finalResourcePackPath); + String sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { JsonObject apiArg = new JsonObject(); apiArg.addProperty("path", uploadPath); @@ -115,7 +109,7 @@ public class DropboxHost implements ResourcePackHost { .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/octet-stream") .header("Dropbox-API-Arg", apiArg.toString()) - .POST(HttpRequest.BodyPublishers.ofFile(finalResourcePackPath)) + .POST(HttpRequest.BodyPublishers.ofFile(resourcePackPath)) .build(); long uploadStart = System.currentTimeMillis(); @@ -152,26 +146,26 @@ public class DropboxHost implements ResourcePackHost { } private String getDownloadUrl() { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { try { JsonObject requestJson = new JsonObject(); - requestJson.addProperty("path", uploadPath); + requestJson.addProperty("path", this.uploadPath); JsonObject settingsJson = new JsonObject(); settingsJson.addProperty("requested_visibility", "public"); requestJson.add("settings", settingsJson); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings")) - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + this.accessToken) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestJson.toString())) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() == 409) { JsonObject listJson = new JsonObject(); - listJson.addProperty("path", uploadPath); + listJson.addProperty("path", this.uploadPath); HttpRequest listLinksRequest = HttpRequest.newBuilder() .uri(URI.create("https://api.dropboxapi.com/2/sharing/list_shared_links")) - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + this.accessToken) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(listJson.toString())) .build(); @@ -200,14 +194,13 @@ public class DropboxHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { - String localFilePath = (String) arguments.get("local-file-path"); String accessToken = (String) arguments.get("access-token"); if (accessToken == null || accessToken.isEmpty()) { throw new RuntimeException("Missing 'access-token' for DropboxHost"); } String uploadPath = (String) arguments.getOrDefault("upload-path", "/resource_pack.zip"); ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new DropboxHost(accessToken, uploadPath, proxy, localFilePath); + return new DropboxHost(accessToken, uploadPath, proxy); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 3b9c6d950..e1415dca1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -27,7 +27,6 @@ import java.util.concurrent.CompletableFuture; public class LobFileHost implements ResourcePackHost { public static final Factory FACTORY = new Factory(); - private final Path forcedPackPath; private final String apiKey; private final ProxySelector proxy; private AccountInfo accountInfo; @@ -36,8 +35,7 @@ public class LobFileHost implements ResourcePackHost { private String sha1; private UUID uuid; - public LobFileHost(String localFile, String apiKey, ProxySelector proxy) { - this.forcedPackPath = localFile == null ? null : ResourcePackHost.customPackPath(localFile); + public LobFileHost(String apiKey, ProxySelector proxy) { this.apiKey = apiKey; this.proxy = proxy; this.readCacheFromDisk(); @@ -107,13 +105,9 @@ public class LobFileHost implements ResourcePackHost { public CompletableFuture upload(Path resourcePackPath) { CompletableFuture future = new CompletableFuture<>(); long totalStartTime = System.currentTimeMillis(); - - if (this.forcedPackPath != null) resourcePackPath = forcedPackPath; - Path finalResourcePackPath = resourcePackPath; - CraftEngine.instance().scheduler().executeAsync(() -> { try { - Map hashes = calculateHashes(finalResourcePackPath); + Map hashes = calculateHashes(resourcePackPath); String sha1Hash = hashes.get("SHA-1"); String sha256Hash = hashes.get("SHA-256"); @@ -124,7 +118,7 @@ public class LobFileHost implements ResourcePackHost { .uri(URI.create("https://lobfile.com/api/v3/upload.php")) .header("X-API-Key", apiKey) .header("Content-Type", "multipart/form-data; boundary=" + boundary) - .POST(buildMultipartBody(finalResourcePackPath, sha256Hash, boundary)) + .POST(buildMultipartBody(resourcePackPath, sha256Hash, boundary)) .build(); long uploadStart = System.currentTimeMillis(); @@ -263,13 +257,12 @@ public class LobFileHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { - String localFilePath = (String) arguments.get("local-file-path"); String apiKey = (String) arguments.get("api-key"); if (apiKey == null || apiKey.isEmpty()) { throw new RuntimeException("Missing 'api-key' for LobFileHost"); } ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new LobFileHost(localFilePath, apiKey, proxy); + return new LobFileHost(apiKey, proxy); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java index 5228bdde6..e3613d6d5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java @@ -34,7 +34,6 @@ public class OneDriveHost implements ResourcePackHost { private final String clientSecret; private final ProxySelector proxy; private final String filePath; - private final Path localFilePath; private Tuple refreshToken; private String sha1; private String fileId; @@ -43,13 +42,11 @@ public class OneDriveHost implements ResourcePackHost { String clientSecret, String refreshToken, String filePath, - String localFilePath, ProxySelector proxy) { this.clientId = clientId; this.clientSecret = clientSecret; this.proxy = proxy; this.filePath = filePath; - this.localFilePath = localFilePath == null ? null : Path.of(localFilePath); this.refreshToken = Tuple.of(refreshToken, "", new Date()); readCacheFromDisk(); } @@ -140,17 +137,15 @@ public class OneDriveHost implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { CompletableFuture future = new CompletableFuture<>(); - if (this.localFilePath != null) resourcePackPath = this.localFilePath; - Path finalResourcePackPath = resourcePackPath; CraftEngine.instance().scheduler().executeAsync(() -> { - sha1 = HashUtils.calculateLocalFileSha1(finalResourcePackPath); + sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); String accessToken = getOrRefreshJwtToken(); try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://graph.microsoft.com/v1.0/drive/root:/" + filePath + ":/content")) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/octet-stream") - .PUT(HttpRequest.BodyPublishers.ofFile(finalResourcePackPath)) + .PUT(HttpRequest.BodyPublishers.ofFile(resourcePackPath)) .build(); long uploadStart = System.currentTimeMillis(); CraftEngine.instance().logger().info("[OneDrive] Starting file upload..."); @@ -242,9 +237,8 @@ public class OneDriveHost implements ResourcePackHost { if (filePath == null || filePath.isEmpty()) { throw new RuntimeException("Missing 'file-path' for OneDriveHost"); } - String localFilePath = (String) arguments.get("local-file-path"); ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new OneDriveHost(clientId, clientSecret, refreshToken, filePath, localFilePath, proxy); + return new OneDriveHost(clientId, clientSecret, refreshToken, filePath, proxy); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index fc55dfb17..97d04f4cd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -38,41 +38,36 @@ import java.util.concurrent.CompletionException; public class S3Host implements ResourcePackHost { public static final Factory FACTORY = new Factory(); private final S3AsyncClient s3AsyncClient; - private final S3Presigner presigner; + private final S3Presigner preSigner; private final String bucket; private final String uploadPath; private final String cdnDomain; private final String cdnProtocol; private final Duration validity; - private final Path localFilePath; public S3Host( S3AsyncClient s3AsyncClient, - S3Presigner presigner, + S3Presigner preSigner, String bucket, String uploadPath, String cdnDomain, String cdnProtocol, - Duration validity, - String localFilePath + Duration validity ) { this.s3AsyncClient = s3AsyncClient; - this.presigner = presigner; + this.preSigner = preSigner; this.bucket = bucket; this.uploadPath = uploadPath; this.cdnDomain = cdnDomain; this.cdnProtocol = cdnProtocol; this.validity = validity; - this.localFilePath = localFilePath == null ? null : ResourcePackHost.customPackPath(localFilePath); } @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { - String objectKey = uploadPath; - - return s3AsyncClient.headObject(HeadObjectRequest.builder() - .bucket(bucket) - .key(objectKey) + return this.s3AsyncClient.headObject(HeadObjectRequest.builder() + .bucket(this.bucket) + .key(this.uploadPath) .build()) .handle((headResponse, exception) -> { if (exception != null) { @@ -90,16 +85,16 @@ public class S3Host implements ResourcePackHost { } String sha1 = headResponse.metadata().get("sha1"); if (sha1 == null) { - CraftEngine.instance().logger().warn("[S3] SHA1 metadata missing for object: " + objectKey); - throw new CompletionException(new IllegalStateException("SHA1 metadata missing for object: " + objectKey)); + CraftEngine.instance().logger().warn("[S3] SHA1 metadata missing for object: " + this.uploadPath); + throw new CompletionException(new IllegalStateException("SHA1 metadata missing for object: " + this.uploadPath)); } GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() - .signatureDuration(validity) - .getObjectRequest(b -> b.bucket(bucket).key(objectKey)) + .signatureDuration(this.validity) + .getObjectRequest(b -> b.bucket(this.bucket).key(this.uploadPath)) .build(); return Collections.singletonList( ResourcePackDownloadData.of( - replaceWithCdnUrl(presigner.presignGetObject(presignRequest).url()), + replaceWithCdnUrl(this.preSigner.presignGetObject(presignRequest).url()), UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), sha1 ) @@ -109,30 +104,28 @@ public class S3Host implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { - if (this.localFilePath != null) resourcePackPath = this.localFilePath; - String objectKey = uploadPath; String sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); PutObjectRequest putObjectRequest = PutObjectRequest.builder() - .bucket(bucket) - .key(objectKey) + .bucket(this.bucket) + .key(this.uploadPath) .metadata(Map.of("sha1", sha1)) .build(); long uploadStart = System.currentTimeMillis(); CraftEngine.instance().logger().info("[S3] Starting file upload..."); - return s3AsyncClient.putObject(putObjectRequest, AsyncRequestBody.fromFile(resourcePackPath)) + return this.s3AsyncClient.putObject(putObjectRequest, AsyncRequestBody.fromFile(resourcePackPath)) .handle((response, exception) -> { if (exception != null) { Throwable cause = exception instanceof CompletionException ? exception.getCause() : exception; CraftEngine.instance().logger().warn( - "[S3] Upload to " + objectKey + " failed! Reason: " + + "[S3] Upload to " + this.uploadPath + " failed! Reason: " + cause.getClass().getSimpleName() + " - " + cause.getMessage() ); throw new CompletionException("Resource pack upload failed", cause); } CraftEngine.instance().logger().info( - "[S3] Upload to " + objectKey + " complete! Took " + + "[S3] Upload to " + this.uploadPath + " complete! Took " + (System.currentTimeMillis() - uploadStart) + "ms" ); return null; @@ -140,8 +133,8 @@ public class S3Host implements ResourcePackHost { } private String replaceWithCdnUrl(URL originalUrl) { - if (cdnDomain == null) return originalUrl.toString(); - return cdnProtocol + "://" + cdnDomain + if (this.cdnDomain == null) return originalUrl.toString(); + return this.cdnProtocol + "://" + this.cdnDomain + originalUrl.getPath() + (originalUrl.getQuery() != null ? "?" + originalUrl.getQuery() : ""); } @@ -174,7 +167,6 @@ public class S3Host implements ResourcePackHost { if (uploadPath == null || uploadPath.isEmpty()) { throw new IllegalArgumentException("'upload-path' cannot be empty for S3 host"); } - String localFilePath = (String) arguments.get("local-file-path"); boolean useLegacySignature = (boolean) arguments.getOrDefault("use-legacy-signature", true); Duration validity = Duration.ofSeconds((int) arguments.getOrDefault("validity", 10)); @@ -219,22 +211,13 @@ public class S3Host implements ResourcePackHost { S3AsyncClient s3AsyncClient = s3AsyncClientBuilder.build(); - S3Presigner presigner = S3Presigner.builder() + S3Presigner preSigner = S3Presigner.builder() .endpointOverride(URI.create(protocol + "://" + endpoint)) .region(Region.of(region)) .credentialsProvider(StaticCredentialsProvider.create(credentials)) .build(); - return new S3Host( - s3AsyncClient, - presigner, - bucket, - uploadPath, - cdnDomain, - cdnProtocol, - validity, - localFilePath - ); + return new S3Host(s3AsyncClient, preSigner, bucket, uploadPath, cdnDomain, cdnProtocol, validity); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index b50b1d34d..ce881c3bb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.MiscUtils; import java.nio.file.Path; @@ -17,7 +18,7 @@ public class SelfHost implements ResourcePackHost { private static final SelfHost INSTANCE = new SelfHost(); public SelfHost() { - SelfHostHttpServer.instance().readResourcePack(CraftEngine.instance().packManager().resourcePackPath()); + SelfHostHttpServer.instance().readResourcePack(Config.fileToUpload()); } @Override @@ -56,7 +57,6 @@ public class SelfHost implements ResourcePackHost { } String protocol = (String) arguments.getOrDefault("protocol", "http"); boolean denyNonMinecraftRequest = (boolean) arguments.getOrDefault("deny-non-minecraft-request", true); - String localFilePath = (String) arguments.get("local-file-path"); Map rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true); int maxRequests = 5; int resetInterval = 20_000; @@ -64,7 +64,7 @@ public class SelfHost implements ResourcePackHost { maxRequests = (int) rateMap.getOrDefault("max-requests", 5); resetInterval = (int) rateMap.getOrDefault("reset-interval", 20) * 1000; } - selfHostHttpServer.updateProperties(ip, port, denyNonMinecraftRequest, protocol, localFilePath, maxRequests, resetInterval); + selfHostHttpServer.updateProperties(ip, port, denyNonMinecraftRequest, protocol, maxRequests, resetInterval); return INSTANCE; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 48b2ad634..67685b733 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -6,9 +6,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.plugin.CraftEngine; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -55,18 +53,15 @@ public class SelfHostHttpServer { private volatile byte[] resourcePackBytes; private String packHash; private UUID packUUID; - private Path localFilePath = null; public void updateProperties(String ip, int port, boolean denyNonMinecraft, String protocol, - String localFile, int maxRequests, int resetInternal) { this.ip = ip; this.port = port; - this.localFilePath = localFile == null ? null : ResourcePackHost.customPackPath(localFile); this.denyNonMinecraft = denyNonMinecraft; this.protocol = protocol; this.rateLimit = maxRequests; @@ -116,7 +111,6 @@ public class SelfHostHttpServer { } public void readResourcePack(Path path) { - if (this.localFilePath != null) path = this.localFilePath; try { if (Files.exists(path)) { this.resourcePackBytes = Files.readAllBytes(path); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index cd862445c..98a3275e6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -79,9 +79,11 @@ public class Config { protected float resource_pack$supported_version$min; protected float resource_pack$supported_version$max; - protected boolean resource_pack$send$kick_if_declined; - protected boolean resource_pack$send$send_on_join; - protected boolean resource_pack$send$send_on_reload; + protected boolean resource_pack$delivery$kick_if_declined; + protected boolean resource_pack$delivery$send_on_join; + protected boolean resource_pack$delivery$resend_on_upload; + protected boolean resource_pack$delivery$auto_upload; + protected Path resource_pack$delivery$file_to_upload; protected Component resource_pack$send$prompt; protected int performance$max_block_chain_update_limit; @@ -202,11 +204,12 @@ public class Config { resource_pack$supported_version$min = getVersion(config.get("resource-pack.supported-version.min", "1.20").toString()); resource_pack$supported_version$max = getVersion(config.get("resource-pack.supported-version.max", "LATEST").toString()); resource_pack$merge_external_folders = config.getStringList("resource-pack.merge-external-folders"); - //resource_pack$send$mode = HostMode.valueOf(config.getString("resource-pack.send.mode", "self-host").replace("-", "_").toUpperCase(Locale.ENGLISH)); - resource_pack$send$send_on_join = config.getBoolean("resource-pack.send.send-on-join", true); - resource_pack$send$send_on_reload = config.getBoolean("resource-pack.send.send-on-reload", true); - resource_pack$send$kick_if_declined = config.getBoolean("resource-pack.send.kick-if-declined", true); - resource_pack$send$prompt = AdventureHelper.miniMessage().deserialize(config.getString("resource-pack.send.prompt", "To fully experience our server, please accept our custom resource pack.")); + resource_pack$delivery$send_on_join = config.getBoolean("resource-pack.delivery.send-on-join", true); + resource_pack$delivery$resend_on_upload = config.getBoolean("resource-pack.delivery.resend-on-upload", true); + resource_pack$delivery$kick_if_declined = config.getBoolean("resource-pack.delivery.kick-if-declined", true); + resource_pack$delivery$auto_upload = config.getBoolean("resource-pack.delivery.auto-upload", true); + resource_pack$delivery$file_to_upload = resolvePath(config.getString("resource-pack.delivery.file-to-upload", "./generated/resource_pack.zip")); + resource_pack$send$prompt = AdventureHelper.miniMessage().deserialize(config.getString("resource-pack.delivery.prompt", "To fully experience our server, please accept our custom resource pack.")); resource_pack$protection$crash_tools$method_1 = config.getBoolean("resource-pack.protection.crash-tools.method-1", false); resource_pack$protection$crash_tools$method_2 = config.getBoolean("resource-pack.protection.crash-tools.method-2", false); resource_pack$protection$crash_tools$method_3 = config.getBoolean("resource-pack.protection.crash-tools.method-3", false); @@ -430,7 +433,7 @@ public class Config { } public static boolean kickOnDeclined() { - return instance.resource_pack$send$kick_if_declined; + return instance.resource_pack$delivery$kick_if_declined; } public static Component resourcePackPrompt() { @@ -438,11 +441,19 @@ public class Config { } public static boolean sendPackOnJoin() { - return instance.resource_pack$send$send_on_join; + return instance.resource_pack$delivery$send_on_join; } - public static boolean sendPackOnReload() { - return instance.resource_pack$send$send_on_reload; + public static boolean sendPackOnUpload() { + return instance.resource_pack$delivery$resend_on_upload; + } + + public static boolean autoUpload() { + return instance.resource_pack$delivery$auto_upload; + } + + public static Path fileToUpload() { + return instance.resource_pack$delivery$file_to_upload; } public static List resolutions() { @@ -687,6 +698,10 @@ public class Config { return configFile; } + private Path resolvePath(String path) { + return path.startsWith(".") ? CraftEngine.instance().dataFolderPath().resolve(path) : Path.of(path); + } + public YamlDocument settings() { if (config == null) { throw new IllegalStateException("Main config not loaded"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index aacfc4f47..c457cac1c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -9,8 +9,8 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser; -import net.momirealms.craftengine.core.plugin.gui.Ingredient; import net.momirealms.craftengine.core.plugin.gui.*; +import net.momirealms.craftengine.core.plugin.gui.Ingredient; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index 277bc4607..12e27e285 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -4,7 +4,8 @@ import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; -import java.net.*; +import java.net.InetSocketAddress; +import java.net.ProxySelector; import java.util.ArrayList; import java.util.List; import java.util.Map; From bb3ced5881a7355283266db4f405e4c94e227962 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 19:42:57 +0800 Subject: [PATCH 32/62] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/src/main/resources/config.yml | 2 +- .../core/pack/host/impl/AlistHost.java | 107 +++++++++++------- .../core/pack/host/impl/DropboxHost.java | 36 +++--- .../core/pack/host/impl/LobFileHost.java | 28 ++--- .../core/pack/host/impl/OneDriveHost.java | 85 +++++++------- .../core/pack/host/impl/S3Host.java | 24 ++-- .../pack/host/impl/SelfHostHttpServer.java | 1 - 7 files changed, 152 insertions(+), 131 deletions(-) diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index d4eb04482..d9fc6deed 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -74,7 +74,7 @@ resource-pack: # If using BungeeCord or Velocity, consider using a proxy-side plugin to handle resource pack delivery. # Read this page for more host types: https://mo-mi.gitbook.io/xiaomomi-plugins/craftengine/plugin-wiki/craftengine/resource-pack/host hosting: - - type: "self_host" + - type: "self" ip: "localhost" port: 8163 protocol: "http" diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 1f27a3bb5..7ad890871 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.pack.host.impl; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; @@ -14,6 +15,7 @@ import net.momirealms.craftengine.core.util.Pair; import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.ProxySelector; import java.net.URI; import java.net.URLEncoder; @@ -23,6 +25,7 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Duration; @@ -41,7 +44,7 @@ public class AlistHost implements ResourcePackHost { private final boolean disabledUpload; private final ProxySelector proxy; private Pair jwtToken; - private String cacheSha1; + private String cachedSha1; public AlistHost(String apiUrl, String userName, @@ -67,19 +70,37 @@ public class AlistHost implements ResourcePackHost { private void readCacheFromDisk() { Path cachePath = CraftEngine.instance().dataFolderPath().resolve("alist.cache"); if (!Files.exists(cachePath)) return; + try (InputStream is = Files.newInputStream(cachePath)) { - cacheSha1 = new String(is.readAllBytes(), StandardCharsets.UTF_8); - } catch (IOException e) { - CraftEngine.instance().logger().warn("[Alist] Failed to read cache file", e); + Map cache = GsonHelper.get().fromJson( + new InputStreamReader(is), + new TypeToken>(){}.getType() + ); + + this.cachedSha1 = cache.get("sha1"); + + CraftEngine.instance().logger().info("[Alist] Loaded cached resource pack metadata"); + } catch (Exception e) { + CraftEngine.instance().logger().warn( + "[Alist] Failed to load cache from disk: " + e.getMessage()); } } private void saveCacheToDisk() { + Map cache = new HashMap<>(); + cache.put("sha1", this.cachedSha1 != null ? this.cachedSha1 : ""); + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("alist.cache"); try { - Files.writeString(cachePath, cacheSha1); + Files.writeString( + cachePath, + GsonHelper.get().toJson(cache), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ); } catch (IOException e) { - CraftEngine.instance().logger().warn("[Alist] Failed to write cache file", e); + CraftEngine.instance().logger().warn( + "[Alist] Failed to persist cache to disk: " + e.getMessage()); } } @@ -87,9 +108,9 @@ public class AlistHost implements ResourcePackHost { public CompletableFuture> requestResourcePackDownloadLink(UUID player) { CompletableFuture> future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(apiUrl + "/api/fs/get")) + .uri(URI.create(this.apiUrl + "/api/fs/get")) .header("Authorization", getOrRefreshJwtToken()) .header("Content-Type", "application/json") .POST(getRequestResourcePackDownloadLinkPost()) @@ -97,7 +118,7 @@ public class AlistHost implements ResourcePackHost { client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenAccept(response -> handleResourcePackDownloadLinkResponse(response, future)) .exceptionally(ex -> { - CraftEngine.instance().logger().severe("[Alist] Failed to request resource pack download link", ex); + CraftEngine.instance().logger().severe("[Alist] Failed to retrieve resource pack download URL", ex); future.completeExceptionally(ex); return null; }); @@ -108,33 +129,33 @@ public class AlistHost implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { - if (disabledUpload) { - cacheSha1 = ""; + if (this.disabledUpload) { + this.cachedSha1 = ""; saveCacheToDisk(); return CompletableFuture.completedFuture(null); } CompletableFuture future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(apiUrl + "/api/fs/put")) + .uri(URI.create(this.apiUrl + "/api/fs/put")) .header("Authorization", getOrRefreshJwtToken()) - .header("File-Path", URLEncoder.encode(filePath, StandardCharsets.UTF_8) + .header("File-Path", URLEncoder.encode(this.filePath, StandardCharsets.UTF_8) .replace("/", "%2F")) .header("overwrite", "true") - .header("password", filePassword) + .header("password", this.filePassword) .header("Content-Type", "application/x-zip-compressed") .PUT(HttpRequest.BodyPublishers.ofFile(resourcePackPath)) .build(); long requestStart = System.currentTimeMillis(); - CraftEngine.instance().logger().info("[Alist] Starting file upload..."); + CraftEngine.instance().logger().info("[Alist] Initiating resource pack upload..."); client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenAccept(response -> { long uploadTime = System.currentTimeMillis() - requestStart; if (response.statusCode() == 200) { - cacheSha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); + this.cachedSha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); saveCacheToDisk(); - CraftEngine.instance().logger().info("[Alist] Upload resource pack successfully in " + uploadTime + "ms"); + CraftEngine.instance().logger().info("[Alist] Successfully uploaded resource pack in " + uploadTime + " ms"); future.complete(null); } else { future.completeExceptionally(new RuntimeException("Upload failed with status code: " + response.statusCode())); @@ -143,7 +164,7 @@ public class AlistHost implements ResourcePackHost { .exceptionally(ex -> { long uploadTime = System.currentTimeMillis() - requestStart; CraftEngine.instance().logger().severe( - "[Alist] Failed to upload resource pack after " + uploadTime + "ms", ex); + "[Alist] Resource pack upload failed after " + uploadTime + " ms", ex); future.completeExceptionally(ex); return null; }); @@ -157,16 +178,16 @@ public class AlistHost implements ResourcePackHost { @Nullable private String getOrRefreshJwtToken() { - if (jwtToken == null || jwtToken.right().before(new Date())) { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + if (this.jwtToken == null || this.jwtToken.right().before(new Date())) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(apiUrl + "/api/auth/login")) + .uri(URI.create(this.apiUrl + "/api/auth/login")) .header("Content-Type", "application/json") .POST(getLoginPost()) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { - CraftEngine.instance().logger().warn("[Alist] Failed to get JWT token: " + response.body()); + CraftEngine.instance().logger().warn("[Alist] Authentication failed (HTTP " + response.statusCode() + "): " + response.body()); return null; } JsonObject jsonData = GsonHelper.parseJsonToJsonObject(response.body()); @@ -175,36 +196,36 @@ public class AlistHost implements ResourcePackHost { JsonElement data = jsonData.get("data"); if (data.isJsonObject()) { JsonObject jsonObj = data.getAsJsonObject(); - jwtToken = Pair.of( + this.jwtToken = Pair.of( jsonObj.getAsJsonPrimitive("token").getAsString(), - new Date(System.currentTimeMillis() + jwtTokenExpiration.toMillis()) + new Date(System.currentTimeMillis() + this.jwtTokenExpiration.toMillis()) ); - return jwtToken.left(); + return this.jwtToken.left(); } - CraftEngine.instance().logger().warn("[Alist] Failed to get JWT token: " + response.body()); + CraftEngine.instance().logger().warn("[Alist] Invalid JWT response format: " + response.body()); return null; } - CraftEngine.instance().logger().warn("[Alist] Failed to get JWT token: " + response.body()); + CraftEngine.instance().logger().warn("[Alist] Authentication rejected: " + response.body()); return null; } catch (IOException | InterruptedException e) { - CraftEngine.instance().logger().warn("[Alist] Failed to get JWT token", e); + CraftEngine.instance().logger().warn("[Alist] JWT token acquisition failed", e); return null; } } - return jwtToken.left(); + return this.jwtToken.left(); } private HttpRequest.BodyPublisher getLoginPost() { - String body = "{\"username\":\"" + userName + "\",\"password\":\"" + password + "\""; - if (otpCode != null && !otpCode.isEmpty()) { - body += ",\"otp_code\":\"" + otpCode + "\""; + String body = "{\"username\":\"" + this.userName + "\",\"password\":\"" + this.password + "\""; + if (this.otpCode != null && !this.otpCode.isEmpty()) { + body += ",\"otp_code\":\"" + this.otpCode + "\""; } body += "}"; return HttpRequest.BodyPublishers.ofString(body); } private HttpRequest.BodyPublisher getRequestResourcePackDownloadLinkPost() { - String body = "{\"path\":\"" + filePath + "\",\"password\":\"" + filePassword + "\"}"; + String body = "{\"path\":\"" + this.filePath + "\",\"password\":\"" + this.filePassword + "\"}"; return HttpRequest.BodyPublishers.ofString(body); } @@ -220,8 +241,8 @@ public class AlistHost implements ResourcePackHost { boolean isDir = dataObj.getAsJsonPrimitive("is_dir").getAsBoolean(); if (!isDir) { String url = dataObj.getAsJsonPrimitive("raw_url").getAsString(); - if ((cacheSha1 == null || cacheSha1.isEmpty()) && disabledUpload) { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + if ((this.cachedSha1 == null || this.cachedSha1.isEmpty()) && this.disabledUpload) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() @@ -235,26 +256,26 @@ public class AlistHost implements ResourcePackHost { md.update(buffer, 0, len); } byte[] digest = md.digest(); - cacheSha1 = HexFormat.of().formatHex(digest); + this.cachedSha1 = HexFormat.of().formatHex(digest); saveCacheToDisk(); } catch (NoSuchAlgorithmException e) { - future.completeExceptionally(new RuntimeException("Failed to get resource pack hash")); + future.completeExceptionally(new RuntimeException("Failed to calculate SHA-1 hash algorithm", e)); return; } } catch (IOException | InterruptedException e) { - future.completeExceptionally(new RuntimeException("Failed to get resource pack hash")); + future.completeExceptionally(new RuntimeException("Failed to retrieve remote resource pack for hashing", e)); return; } } - UUID uuid = UUID.nameUUIDFromBytes(Objects.requireNonNull(cacheSha1).getBytes(StandardCharsets.UTF_8)); - future.complete(List.of(new ResourcePackDownloadData(url, uuid, cacheSha1))); + UUID uuid = UUID.nameUUIDFromBytes(Objects.requireNonNull(this.cachedSha1).getBytes(StandardCharsets.UTF_8)); + future.complete(List.of(new ResourcePackDownloadData(url, uuid, this.cachedSha1))); return; } } } } future.completeExceptionally( - new RuntimeException("Failed to request resource pack download link: " + response.body())); + new RuntimeException("Failed to obtain resource pack download URL (HTTP " + response.statusCode() + "): " + response.body())); } public static class Factory implements ResourcePackHostFactory { @@ -285,4 +306,4 @@ public class AlistHost implements ResourcePackHost { return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, disabledUpload, proxy); } } -} +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java index 1d9252667..167cc503d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -62,10 +62,10 @@ public class DropboxHost implements ResourcePackHost { this.uuid = UUID.fromString(uuidString); } - CraftEngine.instance().logger().info("[DropBox] Loaded cached resource pack info"); + CraftEngine.instance().logger().info("[Dropbox] Loaded cached resource pack info"); } catch (Exception e) { CraftEngine.instance().logger().warn( - "[DropBox] Failed to read cache file: " + e.getMessage()); + "[Dropbox] Failed to load cache from disk: " + e.getMessage()); } } @@ -85,14 +85,14 @@ public class DropboxHost implements ResourcePackHost { ); } catch (IOException e) { CraftEngine.instance().logger().warn( - "[DropBox] Failed to save cache: " + e.getMessage()); + "[Dropbox] Failed to persist cache to disk: " + e.getMessage()); } } @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { - if (url == null) return CompletableFuture.completedFuture(Collections.emptyList()); - return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(url, uuid, sha1))); + if (this.url == null) return CompletableFuture.completedFuture(Collections.emptyList()); + return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(this.url, this.uuid, this.sha1))); } @Override @@ -100,25 +100,25 @@ public class DropboxHost implements ResourcePackHost { CompletableFuture future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { String sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { JsonObject apiArg = new JsonObject(); - apiArg.addProperty("path", uploadPath); + apiArg.addProperty("path", this.uploadPath); apiArg.addProperty("mode", "overwrite"); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://content.dropboxapi.com/2/files/upload")) - .header("Authorization", "Bearer " + accessToken) + .header("Authorization", "Bearer " + this.accessToken) .header("Content-Type", "application/octet-stream") .header("Dropbox-API-Arg", apiArg.toString()) .POST(HttpRequest.BodyPublishers.ofFile(resourcePackPath)) .build(); long uploadStart = System.currentTimeMillis(); - CraftEngine.instance().logger().info("[DropBox] Starting file upload..."); + CraftEngine.instance().logger().info("[Dropbox] Initiating resource pack upload"); client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenAccept(response -> { long uploadTime = System.currentTimeMillis() - uploadStart; CraftEngine.instance().logger().info( - "[DropBox] Upload request completed in " + uploadTime + "ms"); + "[Dropbox] Upload completed in " + uploadTime + " ms"); if (response.statusCode() == 200) { this.sha1 = sha1; this.uuid = UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)); @@ -128,16 +128,16 @@ public class DropboxHost implements ResourcePackHost { return; } CraftEngine.instance().logger().warn( - "[DropBox] Upload resource pack failed: " + response.body()); + "[Dropbox] Upload failed (HTTP " + response.statusCode() + "): " + response.body()); future.completeExceptionally(new RuntimeException(response.body())); }) .exceptionally(ex -> { - CraftEngine.instance().logger().warn("[DropBox] Upload resource pack failed", ex); + CraftEngine.instance().logger().warn("[Dropbox] Upload operation failed: " + ex.getMessage()); future.completeExceptionally(ex); return null; }); } catch (FileNotFoundException e) { - CraftEngine.instance().logger().warn("[DropBox] Failed to upload resource pack: " + e.getMessage()); + CraftEngine.instance().logger().warn("[Dropbox] Resource pack file not found: " + e.getMessage()); future.completeExceptionally(e); } }); @@ -178,13 +178,13 @@ public class DropboxHost implements ResourcePackHost { } } } else if (response.statusCode() != 200) { - CraftEngine.instance().logger().warn("[DropBox] Failed to get download url: " + response.body()); + CraftEngine.instance().logger().warn("[Dropbox] Failed to retrieve download URL (HTTP " + response.statusCode() + "): " + response.body()); return null; } JsonObject jsonData = GsonHelper.parseJsonToJsonObject(response.body()); return jsonData.getAsJsonPrimitive("url").getAsString().replace("dl=0", "dl=1"); } catch (IOException | InterruptedException e) { - CraftEngine.instance().logger().warn("[DropBox] Failed to get download url: " + e.getMessage()); + CraftEngine.instance().logger().warn("[Dropbox] Error generating download link: " + e.getMessage()); return null; } } @@ -196,11 +196,11 @@ public class DropboxHost implements ResourcePackHost { public ResourcePackHost create(Map arguments) { String accessToken = (String) arguments.get("access-token"); if (accessToken == null || accessToken.isEmpty()) { - throw new RuntimeException("Missing 'access-token' for DropboxHost"); + throw new IllegalArgumentException("Missing required 'access-token' configuration"); } String uploadPath = (String) arguments.getOrDefault("upload-path", "/resource_pack.zip"); ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new DropboxHost(accessToken, uploadPath, proxy); + return new DropboxHost(accessToken, "/" + uploadPath, proxy); } } -} +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index e1415dca1..889e83889 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -87,18 +87,18 @@ public class LobFileHost implements ResourcePackHost { } public String getSpaceUsageText() { - if (accountInfo == null) return "Usage data not available"; + if (this.accountInfo == null) return "Usage data not available"; return String.format("Storage: %d/%d MB (%.1f%% used)", - accountInfo.getSpaceUsed() / 1_000_000, - accountInfo.getSpaceQuota() / 1_000_000, - (accountInfo.getSpaceUsed() * 100.0) / accountInfo.getSpaceQuota() + this.accountInfo.getSpaceUsed() / 1_000_000, + this.accountInfo.getSpaceQuota() / 1_000_000, + (this.accountInfo.getSpaceUsed() * 100.0) / this.accountInfo.getSpaceQuota() ); } @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { if (url == null) return CompletableFuture.completedFuture(Collections.emptyList()); - return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(url, uuid, sha1))); + return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(this.url, this.uuid, this.sha1))); } @Override @@ -111,12 +111,12 @@ public class LobFileHost implements ResourcePackHost { String sha1Hash = hashes.get("SHA-1"); String sha256Hash = hashes.get("SHA-256"); - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { String boundary = UUID.randomUUID().toString(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lobfile.com/api/v3/upload.php")) - .header("X-API-Key", apiKey) + .header("X-API-Key", this.apiKey) .header("Content-Type", "multipart/form-data; boundary=" + boundary) .POST(buildMultipartBody(resourcePackPath, sha256Hash, boundary)) .build(); @@ -151,10 +151,10 @@ public class LobFileHost implements ResourcePackHost { } public CompletableFuture fetchAccountInfo() { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://lobfile.com/api/v3/rest/get-account-info")) - .header("X-API-Key", apiKey) + .header("X-API-Key", this.apiKey) .GET() .build(); @@ -273,23 +273,23 @@ public class LobFileHost implements ResourcePackHost { private Map account_usage; public String getEmail() { - return (String) account_info.get("email"); + return (String) this.account_info.get("email"); } public int getSpaceQuota() { - return account_limits.getOrDefault("space_quota", 0); + return this.account_limits.getOrDefault("space_quota", 0); } public int getSpaceUsed() { - return account_usage.getOrDefault("space_used", 0); + return this.account_usage.getOrDefault("space_used", 0); } public int getSlotsUsed() { - return account_usage.getOrDefault("slots_used", 0); + return this.account_usage.getOrDefault("slots_used", 0); } public boolean isSuccess() { - return success; + return this.success; } } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java index e3613d6d5..78ef114d1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java @@ -33,7 +33,7 @@ public class OneDriveHost implements ResourcePackHost { private final String clientId; private final String clientSecret; private final ProxySelector proxy; - private final String filePath; + private final String uploadPath; private Tuple refreshToken; private String sha1; private String fileId; @@ -41,12 +41,12 @@ public class OneDriveHost implements ResourcePackHost { public OneDriveHost(String clientId, String clientSecret, String refreshToken, - String filePath, + String uploadPath, ProxySelector proxy) { this.clientId = clientId; this.clientSecret = clientSecret; this.proxy = proxy; - this.filePath = filePath; + this.uploadPath = uploadPath; this.refreshToken = Tuple.of(refreshToken, "", new Date()); readCacheFromDisk(); } @@ -71,7 +71,7 @@ public class OneDriveHost implements ResourcePackHost { CraftEngine.instance().logger().info("[OneDrive] Loaded cached resource pack info"); } catch (Exception e) { CraftEngine.instance().logger().warn( - "[OneDrive] Failed to read cache file: " + e.getMessage()); + "[OneDrive] Failed to load cache from disk: " + e.getMessage()); } } @@ -93,7 +93,7 @@ public class OneDriveHost implements ResourcePackHost { ); } catch (IOException e) { CraftEngine.instance().logger().warn( - "[OneDrive] Failed to save cache: " + e.getMessage()); + "[OneDrive] Failed to persist cache to disk: " + e.getMessage()); } } @@ -101,11 +101,11 @@ public class OneDriveHost implements ResourcePackHost { public CompletableFuture> requestResourcePackDownloadLink(UUID player) { CompletableFuture> future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { String accessToken = getOrRefreshJwtToken(); saveCacheToDisk(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://graph.microsoft.com/v1.0/drive/items/" + fileId)) + .uri(URI.create("https://graph.microsoft.com/v1.0/drive/items/" + this.fileId)) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/octet-stream") .GET() @@ -113,19 +113,19 @@ public class OneDriveHost implements ResourcePackHost { client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenAccept(response -> { if (response.statusCode() != 200) { - CraftEngine.instance().logger().severe("[OneDrive] Failed to request resource pack download link: " + response.body()); - future.completeExceptionally(new IOException("Failed to request resource pack download link: " + response.body())); + CraftEngine.instance().logger().severe("[OneDrive] Failed to retrieve download URL (HTTP " + response.statusCode() + "): " + response.body()); + future.completeExceptionally(new IOException("HTTP " + response.statusCode() + ": " + response.body())); return; } String downloadUrl = GsonHelper.parseJsonToJsonObject(response.body()).get("@microsoft.graph.downloadUrl").getAsString(); future.complete(List.of(new ResourcePackDownloadData( downloadUrl, - UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), - sha1 + UUID.nameUUIDFromBytes(this.sha1.getBytes(StandardCharsets.UTF_8)), + this.sha1 ))); }) .exceptionally(ex -> { - CraftEngine.instance().logger().severe("[OneDrive] Failed to request resource pack download link", ex); + CraftEngine.instance().logger().severe("[OneDrive] Error retrieving download link: " + ex.getMessage()); future.completeExceptionally(ex); return null; }); @@ -138,36 +138,37 @@ public class OneDriveHost implements ResourcePackHost { public CompletableFuture upload(Path resourcePackPath) { CompletableFuture future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { - sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); + this.sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); String accessToken = getOrRefreshJwtToken(); - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://graph.microsoft.com/v1.0/drive/root:/" + filePath + ":/content")) + .uri(URI.create("https://graph.microsoft.com/v1.0/drive/root:/" + this.uploadPath + ":/content")) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/octet-stream") .PUT(HttpRequest.BodyPublishers.ofFile(resourcePackPath)) .build(); long uploadStart = System.currentTimeMillis(); - CraftEngine.instance().logger().info("[OneDrive] Starting file upload..."); + CraftEngine.instance().logger().info("[OneDrive] Initiating resource pack upload..."); client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenAccept(response -> { + long elapsedTime = System.currentTimeMillis() - uploadStart; if (response.statusCode() == 200 || response.statusCode() == 201) { - CraftEngine.instance().logger().info("[OneDrive] Uploaded resource pack in " + (System.currentTimeMillis() - uploadStart) + "ms"); - fileId = GsonHelper.parseJsonToJsonObject(response.body()).get("id").getAsString(); + CraftEngine.instance().logger().info("[OneDrive] Successfully uploaded resource pack in " + elapsedTime + " ms"); + this.fileId = GsonHelper.parseJsonToJsonObject(response.body()).get("id").getAsString(); saveCacheToDisk(); future.complete(null); } else { - CraftEngine.instance().logger().warn("[OneDrive] Failed to upload resource pack: " + response.statusCode()); - future.completeExceptionally(new RuntimeException("Failed to upload resource pack: " + response.statusCode())); + CraftEngine.instance().logger().severe("[OneDrive] Upload failed (HTTP " + response.statusCode() + "): " + response.body()); + future.completeExceptionally(new RuntimeException("HTTP " + response.statusCode() + ": " + response.body())); } }) .exceptionally(ex -> { - CraftEngine.instance().logger().warn("[OneDrive] Failed to upload resource pack", ex); + CraftEngine.instance().logger().severe("[OneDrive] Upload operation failed: " + ex.getMessage()); future.completeExceptionally(ex); return null; }); } catch (FileNotFoundException e) { - CraftEngine.instance().logger().warn("[OneDrive] File not found: " + e.getMessage()); + CraftEngine.instance().logger().warn("[OneDrive] Resource pack file not found: " + e.getMessage()); future.completeExceptionally(e); } }); @@ -175,12 +176,12 @@ public class OneDriveHost implements ResourcePackHost { } private String getOrRefreshJwtToken() { - if (refreshToken == null || refreshToken.mid().isEmpty() || refreshToken.right().before(new Date())) { - try (HttpClient client = HttpClient.newBuilder().proxy(proxy).build()) { - String formData = "client_id=" + URLEncoder.encode(clientId, StandardCharsets.UTF_8) + - "&client_secret=" + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) + + if (this.refreshToken == null || this.refreshToken.mid().isEmpty() || this.refreshToken.right().before(new Date())) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { + String formData = "client_id=" + URLEncoder.encode(this.clientId, StandardCharsets.UTF_8) + + "&client_secret=" + URLEncoder.encode(this.clientSecret, StandardCharsets.UTF_8) + "&redirect_uri=" + URLEncoder.encode("https://alist.nn.ci/tool/onedrive/callback", StandardCharsets.UTF_8) + - "&refresh_token=" + URLEncoder.encode(refreshToken.left(), StandardCharsets.UTF_8) + + "&refresh_token=" + URLEncoder.encode(this.refreshToken.left(), StandardCharsets.UTF_8) + "&grant_type=refresh_token" + "&scope=Files.ReadWrite.All+offline_access"; @@ -193,28 +194,28 @@ public class OneDriveHost implements ResourcePackHost { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { - CraftEngine.instance().logger().warn("[OneDrive] Failed to refresh JWT token: " + response.body()); - return refreshToken != null ? refreshToken.mid() : ""; + CraftEngine.instance().logger().severe("[OneDrive] Authentication failed (HTTP " + response.statusCode() + "): " + response.body()); + return this.refreshToken != null ? this.refreshToken.mid() : ""; } JsonObject jsonData = GsonHelper.parseJsonToJsonObject(response.body()); if (jsonData.has("error")) { CraftEngine.instance().logger().warn("[OneDrive] Token refresh error: " + jsonData); - throw new RuntimeException("Token refresh failed: " + jsonData); + throw new RuntimeException("Authentication error: " + jsonData); } long expiresInMillis = jsonData.get("expires_in").getAsInt() * 1000L; - refreshToken = Tuple.of( + this.refreshToken = Tuple.of( jsonData.get("refresh_token").getAsString(), jsonData.get("access_token").getAsString(), new Date(System.currentTimeMillis() + expiresInMillis - 10_000) ); } catch (IOException | InterruptedException e) { - CraftEngine.instance().logger().warn("[OneDrive] Token refresh failed: " + e.getMessage()); - throw new RuntimeException("Token refresh failed", e); + CraftEngine.instance().logger().severe("[OneDrive] Token refresh failure: " + e.getMessage()); + throw new RuntimeException("Authentication process failed", e); } } - return refreshToken.mid(); + return this.refreshToken.mid(); } public static class Factory implements ResourcePackHostFactory { @@ -223,22 +224,22 @@ public class OneDriveHost implements ResourcePackHost { public ResourcePackHost create(Map arguments) { String clientId = (String) arguments.get("client-id"); if (clientId == null || clientId.isEmpty()) { - throw new RuntimeException("Missing 'client-id' for OneDriveHost"); + throw new IllegalArgumentException("Missing required 'client-id' configuration"); } String clientSecret = (String) arguments.get("client-secret"); if (clientSecret == null || clientSecret.isEmpty()) { - throw new RuntimeException("Missing 'client-secret' for OneDriveHost"); + throw new IllegalArgumentException("Missing required 'client-secret' configuration"); } String refreshToken = (String) arguments.get("refresh-token"); if (refreshToken == null || refreshToken.isEmpty()) { - throw new RuntimeException("Missing 'refresh-token' for OneDriveHost"); + throw new IllegalArgumentException("Missing required 'refresh-token' configuration"); } - String filePath = (String) arguments.getOrDefault("file-path", "resource_pack.zip"); - if (filePath == null || filePath.isEmpty()) { - throw new RuntimeException("Missing 'file-path' for OneDriveHost"); + String uploadPath = (String) arguments.getOrDefault("upload-path", "resource_pack.zip"); + if (uploadPath == null || uploadPath.isEmpty()) { + throw new IllegalArgumentException("Invalid 'upload-path' configuration"); } ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new OneDriveHost(clientId, clientSecret, refreshToken, filePath, proxy); + return new OneDriveHost(clientId, clientSecret, refreshToken, uploadPath, proxy); } } -} +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index 97d04f4cd..5005bf2a5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -73,20 +73,20 @@ public class S3Host implements ResourcePackHost { if (exception != null) { Throwable cause = exception.getCause(); if (cause instanceof NoSuchKeyException) { - CraftEngine.instance().logger().warn("[S3] Resource pack not found! Upload it first."); + CraftEngine.instance().logger().warn("[S3] Resource pack not found in bucket '" + this.bucket + "'. Path: " + this.uploadPath); return Collections.emptyList(); } else { CraftEngine.instance().logger().warn( - "[S3] Requesting resource pack failed! Reason: " + + "[S3] Failed to retrieve resource pack metadata. Reason: " + cause.getClass().getSimpleName() + " - " + cause.getMessage() ); - throw new CompletionException("Failed to request resource pack", cause); + throw new CompletionException("Metadata request failed for path: " + this.uploadPath, cause); } } String sha1 = headResponse.metadata().get("sha1"); if (sha1 == null) { - CraftEngine.instance().logger().warn("[S3] SHA1 metadata missing for object: " + this.uploadPath); - throw new CompletionException(new IllegalStateException("SHA1 metadata missing for object: " + this.uploadPath)); + CraftEngine.instance().logger().warn("[S3] Missing SHA-1 checksum in object metadata. Path: " + this.uploadPath); + throw new CompletionException(new IllegalStateException("Missing SHA-1 metadata for S3 object: " + this.uploadPath)); } GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() .signatureDuration(this.validity) @@ -111,7 +111,7 @@ public class S3Host implements ResourcePackHost { .metadata(Map.of("sha1", sha1)) .build(); long uploadStart = System.currentTimeMillis(); - CraftEngine.instance().logger().info("[S3] Starting file upload..."); + CraftEngine.instance().logger().info("[S3] Initiating resource pack upload to '" + this.uploadPath + "'"); return this.s3AsyncClient.putObject(putObjectRequest, AsyncRequestBody.fromFile(resourcePackPath)) .handle((response, exception) -> { if (exception != null) { @@ -119,14 +119,14 @@ public class S3Host implements ResourcePackHost { exception.getCause() : exception; CraftEngine.instance().logger().warn( - "[S3] Upload to " + this.uploadPath + " failed! Reason: " + + "[S3] Upload failed for path '" + this.uploadPath + "'. Error: " + cause.getClass().getSimpleName() + " - " + cause.getMessage() ); - throw new CompletionException("Resource pack upload failed", cause); + throw new CompletionException("Failed to upload to S3 path: " + this.uploadPath, cause); } CraftEngine.instance().logger().info( - "[S3] Upload to " + this.uploadPath + " complete! Took " + - (System.currentTimeMillis() - uploadStart) + "ms" + "[S3] Successfully uploaded resource pack to '" + this.uploadPath + "' in " + + (System.currentTimeMillis() - uploadStart) + " ms" ); return null; }); @@ -200,7 +200,7 @@ public class S3Host implements ResourcePackHost { String username = (String) proxySetting.get("username"); String password = (String) proxySetting.get("password"); if (host == null || host.isEmpty() || port <= 0 || port > 65535 || scheme == null || scheme.isEmpty()) { - throw new IllegalArgumentException("Invalid proxy setting"); + throw new IllegalArgumentException("Invalid proxy configuration"); } ProxyConfiguration.Builder builder = ProxyConfiguration.builder().host(host).port(port).scheme(scheme); if (username != null) builder.username(username); @@ -220,4 +220,4 @@ public class S3Host implements ResourcePackHost { return new S3Host(s3AsyncClient, preSigner, bucket, uploadPath, cdnDomain, cdnProtocol, validity); } } -} +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 67685b733..cd1666c1f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -61,7 +61,6 @@ public class SelfHostHttpServer { int maxRequests, int resetInternal) { this.ip = ip; - this.port = port; this.denyNonMinecraft = denyNonMinecraft; this.protocol = protocol; this.rateLimit = maxRequests; From efcb9e64188b1c0782712d67d0ea1433b890111f Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 20:04:49 +0800 Subject: [PATCH 33/62] BETA2 --- bukkit/loader/src/main/resources/commands.yml | 7 ++ .../src/main/resources/translations/en.yml | 2 + .../src/main/resources/translations/zh_cn.yml | 2 + .../plugin/command/BukkitCommandManager.java | 3 +- .../plugin/command/feature/ReloadCommand.java | 3 - .../command/feature/UploadPackCommand.java | 37 ++++++++++ .../core/pack/host/ResourcePackHost.java | 6 ++ .../core/pack/host/impl/AlistHost.java | 71 ++++++------------- .../core/pack/host/impl/DropboxHost.java | 12 ++++ .../core/pack/host/impl/ExternalHost.java | 12 ++++ .../core/pack/host/impl/LobFileHost.java | 12 ++++ .../core/pack/host/impl/NoneHost.java | 12 ++++ .../core/pack/host/impl/OneDriveHost.java | 16 +++-- .../core/pack/host/impl/S3Host.java | 12 ++++ .../core/pack/host/impl/SelfHost.java | 12 ++++ .../core/plugin/locale/MessageConstants.java | 2 + gradle.properties | 4 +- 17 files changed, 165 insertions(+), 60 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/UploadPackCommand.java diff --git a/bukkit/loader/src/main/resources/commands.yml b/bukkit/loader/src/main/resources/commands.yml index 81f4a1076..72705d3b8 100644 --- a/bukkit/loader/src/main/resources/commands.yml +++ b/bukkit/loader/src/main/resources/commands.yml @@ -16,6 +16,13 @@ reload: - /craftengine reload - /ce reload +upload: + enable: true + permission: ce.command.admin.upload + usage: + - /craftengine upload + - /ce upload + get_item: enable: true permission: ce.command.admin.get_item diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index eee50f283..e0f2a5355 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -59,6 +59,8 @@ command.resource.enable.failure.unknown: "Unknown resource " command.resource.disable.success: "Disabled resource . Run /ce reload all to apply changes" command.resource.disable.failure.unknown: "Unknown resource " command.resource.list: "Enabled resources(): Disabled resources(): " +command.upload.failure.not_supported: "Current hosting method '' doesn't support uploading resource packs." +command.upload.on_progress: "Started uploading progress. Check the console for more information." warning.config.image.duplicated: "Issue found in file - Duplicated image ''." warning.config.image.lack_height: "Issue found in file - The image '' is missing the required 'height' argument." warning.config.image.height_smaller_than_ascent: "Issue found in file - The image '' violates the bitmap image rule: 'height' should be no lower than 'ascent'." diff --git a/bukkit/loader/src/main/resources/translations/zh_cn.yml b/bukkit/loader/src/main/resources/translations/zh_cn.yml index 0f03c5226..b6db4cf15 100644 --- a/bukkit/loader/src/main/resources/translations/zh_cn.yml +++ b/bukkit/loader/src/main/resources/translations/zh_cn.yml @@ -59,6 +59,8 @@ command.resource.enable.failure.unknown: "未知资源 " command.resource.disable.success: "已禁用 . 执行 /ce reload all 以应用更改" command.resource.disable.failure.unknown: "未知资源 " command.resource.list: "启用的资源(): 禁用的资源(): " +command.upload.failure.not_supported: "当前托管模式 '' 不支持上传资源包." +command.upload.on_progress: "已开始上传进程. 检查控制台以获取详细信息." warning.config.image.duplicated: "在文件 中发现问题 - 图片 '' 重复定义" warning.config.image.lack_height: "在文件 中发现问题 - 图片 '' 缺少必要的 'height' 高度参数" warning.config.image.height_smaller_than_ascent: "在文件 中发现问题 - 图片 '' 违反位图规则:'height' 高度值不应小于 'ascent' 基准线高度" diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index 556ceaf64..322d0985a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java @@ -49,7 +49,8 @@ public class BukkitCommandManager extends AbstractCommandManager new TotemAnimationCommand(this, plugin), new EnableResourceCommand(this, plugin), new DisableResourceCommand(this, plugin), - new ListResourceCommand(this, plugin) + new ListResourceCommand(this, plugin), + new UploadPackCommand(this, plugin) )); final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ReloadCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ReloadCommand.java index 07f41414b..00056ba91 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ReloadCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ReloadCommand.java @@ -61,7 +61,6 @@ public class ReloadCommand extends BukkitCommandFeature { plugin().logger().warn("Failed to reload config", e); } } else if (argument == ReloadArgument.PACK) { - RELOAD_PACK_FLAG = true; plugin().scheduler().executeAsync(() -> { try { long time1 = System.currentTimeMillis(); @@ -73,8 +72,6 @@ public class ReloadCommand extends BukkitCommandFeature { } catch (Exception e) { handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_FAILURE); plugin().logger().warn("Failed to generate resource pack", e); - } finally { - RELOAD_PACK_FLAG = false; } }); } else if (argument == ReloadArgument.ALL) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/UploadPackCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/UploadPackCommand.java new file mode 100644 index 000000000..b7294437d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/UploadPackCommand.java @@ -0,0 +1,37 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.Command; + +public class UploadPackCommand extends BukkitCommandFeature { + + public UploadPackCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .handler(context -> { + ResourcePackHost host = plugin().packManager().resourcePackHost(); + if (host.canUpload()) { + handleFeedback(context, MessageConstants.COMMAND_UPLOAD_ON_PROGRESS); + host.upload(Config.fileToUpload()); + } else { + handleFeedback(context, MessageConstants.COMMAND_UPLOAD_FAILURE_NOT_SUPPORTED, Component.text(host.type().value())); + } + }); + } + + @Override + public String getFeatureID() { + return "upload"; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java index 1da990f1d..0b42f0bd3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java @@ -1,5 +1,7 @@ package net.momirealms.craftengine.core.pack.host; +import net.momirealms.craftengine.core.util.Key; + import java.nio.file.Path; import java.util.List; import java.util.UUID; @@ -10,4 +12,8 @@ public interface ResourcePackHost { CompletableFuture> requestResourcePackDownloadLink(UUID player); CompletableFuture upload(Path resourcePackPath); + + boolean canUpload(); + + Key type(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 7ad890871..19ebe3055 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -6,11 +6,9 @@ import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.GsonHelper; -import net.momirealms.craftengine.core.util.HashUtils; -import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.Pair; +import net.momirealms.craftengine.core.util.*; import javax.annotation.Nullable; import java.io.IOException; @@ -26,8 +24,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -40,8 +36,7 @@ public class AlistHost implements ResourcePackHost { private final String filePassword; private final String otpCode; private final Duration jwtTokenExpiration; - private final String filePath; - private final boolean disabledUpload; + private final String uploadPath; private final ProxySelector proxy; private Pair jwtToken; private String cachedSha1; @@ -52,8 +47,7 @@ public class AlistHost implements ResourcePackHost { String filePassword, String otpCode, Duration jwtTokenExpiration, - String filePath, - boolean disabledUpload, + String uploadPath, ProxySelector proxy) { this.apiUrl = apiUrl; this.userName = userName; @@ -61,12 +55,21 @@ public class AlistHost implements ResourcePackHost { this.filePassword = filePassword; this.otpCode = otpCode; this.jwtTokenExpiration = jwtTokenExpiration; - this.filePath = filePath; - this.disabledUpload = disabledUpload; + this.uploadPath = uploadPath; this.proxy = proxy; this.readCacheFromDisk(); } + @Override + public boolean canUpload() { + return true; + } + + @Override + public Key type() { + return ResourcePackHosts.ALIST; + } + private void readCacheFromDisk() { Path cachePath = CraftEngine.instance().dataFolderPath().resolve("alist.cache"); if (!Files.exists(cachePath)) return; @@ -129,18 +132,13 @@ public class AlistHost implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { - if (this.disabledUpload) { - this.cachedSha1 = ""; - saveCacheToDisk(); - return CompletableFuture.completedFuture(null); - } CompletableFuture future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(this.apiUrl + "/api/fs/put")) .header("Authorization", getOrRefreshJwtToken()) - .header("File-Path", URLEncoder.encode(this.filePath, StandardCharsets.UTF_8) + .header("File-Path", URLEncoder.encode(this.uploadPath, StandardCharsets.UTF_8) .replace("/", "%2F")) .header("overwrite", "true") .header("password", this.filePassword) @@ -225,7 +223,7 @@ public class AlistHost implements ResourcePackHost { } private HttpRequest.BodyPublisher getRequestResourcePackDownloadLinkPost() { - String body = "{\"path\":\"" + this.filePath + "\",\"password\":\"" + this.filePassword + "\"}"; + String body = "{\"path\":\"" + this.uploadPath + "\",\"password\":\"" + this.filePassword + "\"}"; return HttpRequest.BodyPublishers.ofString(body); } @@ -241,32 +239,6 @@ public class AlistHost implements ResourcePackHost { boolean isDir = dataObj.getAsJsonPrimitive("is_dir").getAsBoolean(); if (!isDir) { String url = dataObj.getAsJsonPrimitive("raw_url").getAsString(); - if ((this.cachedSha1 == null || this.cachedSha1.isEmpty()) && this.disabledUpload) { - try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .GET() - .build(); - HttpResponse responseHash = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); - try (InputStream inputStream = responseHash.body()) { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] buffer = new byte[8192]; - int len; - while ((len = inputStream.read(buffer)) != -1) { - md.update(buffer, 0, len); - } - byte[] digest = md.digest(); - this.cachedSha1 = HexFormat.of().formatHex(digest); - saveCacheToDisk(); - } catch (NoSuchAlgorithmException e) { - future.completeExceptionally(new RuntimeException("Failed to calculate SHA-1 hash algorithm", e)); - return; - } - } catch (IOException | InterruptedException e) { - future.completeExceptionally(new RuntimeException("Failed to retrieve remote resource pack for hashing", e)); - return; - } - } UUID uuid = UUID.nameUUIDFromBytes(Objects.requireNonNull(this.cachedSha1).getBytes(StandardCharsets.UTF_8)); future.complete(List.of(new ResourcePackDownloadData(url, uuid, this.cachedSha1))); return; @@ -297,13 +269,12 @@ public class AlistHost implements ResourcePackHost { String filePassword = (String) arguments.getOrDefault("file-password", ""); String otpCode = (String) arguments.get("otp-code"); Duration jwtTokenExpiration = Duration.ofHours((int) arguments.getOrDefault("jwt-token-expiration", 48)); - String filePath = (String) arguments.get("file-path"); - if (filePath == null || filePath.isEmpty()) { - throw new IllegalArgumentException("'file-path' cannot be empty for Alist host"); + String uploadPath = (String) arguments.get("upload-path"); + if (uploadPath == null || uploadPath.isEmpty()) { + throw new IllegalArgumentException("'upload-path' cannot be empty for Alist host"); } - boolean disabledUpload = (boolean) arguments.getOrDefault("disabled-upload", false); ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, filePath, disabledUpload, proxy); + return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, uploadPath, proxy); } } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java index 167cc503d..a3f3edafe 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -6,9 +6,11 @@ import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.HashUtils; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import java.io.FileNotFoundException; @@ -89,6 +91,16 @@ public class DropboxHost implements ResourcePackHost { } } + @Override + public boolean canUpload() { + return true; + } + + @Override + public Key type() { + return ResourcePackHosts.DROPBOX; + } + @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { if (this.url == null) return CompletableFuture.completedFuture(Collections.emptyList()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java index 990f6e9fe..563bc1ca9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java @@ -3,6 +3,8 @@ package net.momirealms.craftengine.core.pack.host.impl; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; +import net.momirealms.craftengine.core.util.Key; import java.nio.file.Path; import java.util.List; @@ -28,6 +30,16 @@ public class ExternalHost implements ResourcePackHost { return CompletableFuture.completedFuture(null); } + @Override + public boolean canUpload() { + return false; + } + + @Override + public Key type() { + return ResourcePackHosts.EXTERNAL; + } + public static class Factory implements ResourcePackHostFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 889e83889..00c0d49df 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -4,8 +4,10 @@ import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import java.io.IOException; @@ -41,6 +43,16 @@ public class LobFileHost implements ResourcePackHost { this.readCacheFromDisk(); } + @Override + public boolean canUpload() { + return true; + } + + @Override + public Key type() { + return ResourcePackHosts.LOBFILE; + } + public void readCacheFromDisk() { Path cachePath = CraftEngine.instance().dataFolderPath().resolve("lobfile.cache"); if (!Files.exists(cachePath)) return; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/NoneHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/NoneHost.java index 6a8d4325b..3802756b5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/NoneHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/NoneHost.java @@ -3,6 +3,8 @@ package net.momirealms.craftengine.core.pack.host.impl; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; +import net.momirealms.craftengine.core.util.Key; import java.nio.file.Path; import java.util.List; @@ -24,6 +26,16 @@ public class NoneHost implements ResourcePackHost { return CompletableFuture.completedFuture(null); } + @Override + public boolean canUpload() { + return false; + } + + @Override + public Key type() { + return ResourcePackHosts.NONE; + } + public static class Factory implements ResourcePackHostFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java index 78ef114d1..9943c3df6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java @@ -5,11 +5,9 @@ import com.google.gson.reflect.TypeToken; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.GsonHelper; -import net.momirealms.craftengine.core.util.HashUtils; -import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.Tuple; +import net.momirealms.craftengine.core.util.*; import java.io.FileNotFoundException; import java.io.IOException; @@ -51,6 +49,16 @@ public class OneDriveHost implements ResourcePackHost { readCacheFromDisk(); } + @Override + public boolean canUpload() { + return true; + } + + @Override + public Key type() { + return ResourcePackHosts.ONEDRIVE; + } + public void readCacheFromDisk() { Path cachePath = CraftEngine.instance().dataFolderPath().resolve("onedrive.cache"); if (!Files.exists(cachePath)) return; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index 5005bf2a5..c5f586fe4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -3,8 +3,10 @@ package net.momirealms.craftengine.core.pack.host.impl; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.HashUtils; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; @@ -63,6 +65,16 @@ public class S3Host implements ResourcePackHost { this.validity = validity; } + @Override + public boolean canUpload() { + return true; + } + + @Override + public Key type() { + return ResourcePackHosts.S3; + } + @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { return this.s3AsyncClient.headObject(HeadObjectRequest.builder() diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index ce881c3bb..fb0a1223c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -3,8 +3,10 @@ package net.momirealms.craftengine.core.pack.host.impl; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import java.nio.file.Path; @@ -42,6 +44,16 @@ public class SelfHost implements ResourcePackHost { return future; } + @Override + public boolean canUpload() { + return true; + } + + @Override + public Key type() { + return ResourcePackHosts.SELF; + } + public static class Factory implements ResourcePackHostFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java index 3d61517ab..29d202578 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java @@ -26,4 +26,6 @@ public interface MessageConstants { TranslatableComponent.Builder COMMAND_RESOURCE_DISABLE_SUCCESS = Component.translatable().key("command.resource.disable.success"); TranslatableComponent.Builder COMMAND_RESOURCE_DISABLE_FAILURE = Component.translatable().key("command.resource.disable.failure.unknown"); TranslatableComponent.Builder COMMAND_RESOURCE_LIST = Component.translatable().key("command.resource.list"); + TranslatableComponent.Builder COMMAND_UPLOAD_FAILURE_NOT_SUPPORTED = Component.translatable().key("command.upload.failure.not_supported"); + TranslatableComponent.Builder COMMAND_UPLOAD_ON_PROGRESS = Component.translatable().key("command.upload.on_progress"); } diff --git a/gradle.properties b/gradle.properties index 270503e76..65fb4f3fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.49-beta.1 +project_version=0.0.49-beta.2 config_version=28 -lang_version=4 +lang_version=5 project_group=net.momirealms latest_supported_version=1.21.5 latest_minecraft_version=1.21.5 From fd9a24a718aad91b82808f7f6fcd26426c2673d6 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 20:09:22 +0800 Subject: [PATCH 34/62] =?UTF-8?q?=E6=8F=90=E5=8F=96=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/pack/BukkitPackManager.java | 5 +++++ .../bukkit/plugin/command/feature/UploadPackCommand.java | 3 +-- .../net/momirealms/craftengine/core/pack/PackManager.java | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index d0134f042..0c6589d7a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -121,6 +121,11 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { @EventHandler(priority = EventPriority.MONITOR) public void onAsyncResourcePackGenerate(AsyncResourcePackGenerateEvent event) { if (!Config.autoUpload()) return; + + } + + @Override + public void uploadResourcePack() { resourcePackHost().upload(Config.fileToUpload()).whenComplete((d, e) -> { if (e != null) { CraftEngine.instance().logger().warn("Failed to upload resource pack", e); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/UploadPackCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/UploadPackCommand.java index b7294437d..1450b37e9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/UploadPackCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/UploadPackCommand.java @@ -5,7 +5,6 @@ import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; -import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.locale.MessageConstants; import org.bukkit.command.CommandSender; import org.incendo.cloud.Command; @@ -23,7 +22,7 @@ public class UploadPackCommand extends BukkitCommandFeature { ResourcePackHost host = plugin().packManager().resourcePackHost(); if (host.canUpload()) { handleFeedback(context, MessageConstants.COMMAND_UPLOAD_ON_PROGRESS); - host.upload(Config.fileToUpload()); + plugin().packManager().uploadResourcePack(); } else { handleFeedback(context, MessageConstants.COMMAND_UPLOAD_FAILURE_NOT_SUPPORTED, Component.text(host.type().value())); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java index ae3552373..54f85bd89 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java @@ -36,4 +36,6 @@ public interface PackManager extends Manageable { Path resourcePackPath(); ResourcePackHost resourcePackHost(); + + void uploadResourcePack(); } From 3ba9c8d9bd52bd932c5d4cf5650eb51ec2d2567e Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 20:11:15 +0800 Subject: [PATCH 35/62] Update BukkitPackManager.java --- .../momirealms/craftengine/bukkit/pack/BukkitPackManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 0c6589d7a..ecab33493 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -121,7 +121,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { @EventHandler(priority = EventPriority.MONITOR) public void onAsyncResourcePackGenerate(AsyncResourcePackGenerateEvent event) { if (!Config.autoUpload()) return; - + uploadResourcePack(); } @Override From 9b5d20f21270bdd10f0389f7e429a2cf58af36b1 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 20:52:04 +0800 Subject: [PATCH 36/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=BD=AF=E7=A2=B0?= =?UTF-8?q?=E6=92=9E=E4=BD=93=E7=A7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../furniture/hitbox/BukkitHitBoxTypes.java | 1 + .../entity/furniture/hitbox/CustomHitBox.java | 87 +++++++++++++++++++ .../furniture/hitbox/InteractionHitBox.java | 2 +- .../furniture/hitbox/ShulkerHitBox.java | 4 +- .../bukkit/plugin/BukkitCraftEngine.java | 1 + .../plugin/command/feature/TestCommand.java | 9 +- .../core/entity/furniture/HitBoxTypes.java | 1 + gradle.properties | 2 +- 8 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitHitBoxTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitHitBoxTypes.java index 19aab93a6..dda752638 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitHitBoxTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/BukkitHitBoxTypes.java @@ -10,5 +10,6 @@ public class BukkitHitBoxTypes extends HitBoxTypes { register(INTERACTION, InteractionHitBox.FACTORY); register(SHULKER, ShulkerHitBox.FACTORY); register(HAPPY_GHAST, HappyGhastHitBox.FACTORY); + register(CUSTOM, CustomHitBox.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java new file mode 100644 index 000000000..f3355e535 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java @@ -0,0 +1,87 @@ +package net.momirealms.craftengine.bukkit.entity.furniture.hitbox; + +import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.core.entity.furniture.*; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.VersionHelper; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.entity.EntityType; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class CustomHitBox extends AbstractHitBox { + public static final Factory FACTORY = new Factory(); + private final float scale; + private final EntityType entityType; + private final List cachedValues = new ArrayList<>(); + + public CustomHitBox(Seat[] seats, Vector3f position, EntityType type, float scale) { + super(seats, position); + this.scale = scale; + this.entityType = type; + BaseEntityData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedValues); + BaseEntityData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedValues); + BaseEntityData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedValues); + } + + public EntityType entityType() { + return entityType; + } + + public float scale() { + return scale; + } + + @Override + public Key type() { + return HitBoxTypes.CUSTOM; + } + + @Override + public void addSpawnPackets(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets) { + Vector3f offset = conjugated.transform(new Vector3f(position())); + try { + packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + entityId[0], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw, + FastNMS.INSTANCE.toNMSEntityType(this.entityType), 0, Reflections.instance$Vec3$Zero, 0 + ), true); + packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId[0], List.copyOf(this.cachedValues)), true); + if (VersionHelper.isVersionNewerThan1_20_5() && this.scale != 1) { + Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Holder$Attribute$scale, (Consumer) (o) -> {}); + Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale); + packets.accept(Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityId[0], Collections.singletonList(attributeInstance)), false); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to construct custom hitbox spawn packet", e); + } + } + + @Override + public int[] acquireEntityIds(Supplier entityIdSupplier) { + return new int[] {entityIdSupplier.get()}; + } + + public static class Factory implements HitBoxFactory { + + @Override + public HitBox create(Map arguments) { + Vector3f position = MiscUtils.getVector3f(arguments.getOrDefault("position", "0")); + float scale = MiscUtils.getAsFloat(arguments.getOrDefault("scale", "1")); + EntityType entityType = Registry.ENTITY_TYPE.get(new NamespacedKey("minecraft", (String) arguments.getOrDefault("entity-type", "slime"))); + if (entityType == null) { + throw new IllegalArgumentException("EntityType not found: " + arguments.get("entity-type")); + } + return new CustomHitBox(HitBoxFactory.getSeats(arguments), position, entityType, scale); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java index d895dfb88..a1daf61e9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java @@ -55,7 +55,7 @@ public class InteractionHitBox extends AbstractHitBox { ), true); packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId[0], List.copyOf(this.cachedValues)), true); } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to construct hitbox spawn packet", e); + throw new RuntimeException("Failed to construct interaction hitbox spawn packet", e); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java index 583bdef6b..175f8ca20 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java @@ -140,9 +140,9 @@ public class ShulkerHitBox extends AbstractHitBox { entityIds[1], (short) 0, ya, (short) 0, true ), false); } - if (VersionHelper.isVersionNewerThan1_20_5()) { + if (VersionHelper.isVersionNewerThan1_20_5() && this.scale != 1) { Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Holder$Attribute$scale, (Consumer) (o) -> {}); - Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, scale); + Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale); packets.accept(Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance)), false); } if (this.interactionEntity) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java index 891850e23..4825024e3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java @@ -43,6 +43,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Pose; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java index 3bdeee1f3..9804bb208 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java @@ -1,14 +1,11 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; -import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; -import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; -import org.bukkit.Material; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; +import org.bukkit.entity.Pose; import org.incendo.cloud.Command; public class TestCommand extends BukkitCommandFeature { @@ -23,10 +20,6 @@ public class TestCommand extends BukkitCommandFeature { .senderType(Player.class) .handler(context -> { Player player = context.sender(); - ItemStack itemStack = new ItemStack(Material.STONE); - Item wrapped = BukkitItemManager.instance().wrap(itemStack); - wrapped.lore(null); - player.getInventory().addItem(wrapped.load()); }); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxTypes.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxTypes.java index 920cabfa1..af2246a5c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxTypes.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBoxTypes.java @@ -14,6 +14,7 @@ public class HitBoxTypes { public static final Key INTERACTION = Key.of("minecraft:interaction"); public static final Key SHULKER = Key.of("minecraft:shulker"); public static final Key HAPPY_GHAST = Key.of("minecraft:happy_ghast"); + public static final Key CUSTOM = Key.of("minecraft:custom"); public static void register(Key key, HitBoxFactory factory) { Holder.Reference holder = ((WritableRegistry) BuiltInRegistries.HITBOX_FACTORY) diff --git a/gradle.properties b/gradle.properties index 65fb4f3fa..f7f5042e2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -51,7 +51,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.13 -nms_helper_version=0.59.4 +nms_helper_version=0.59.5 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 amazon_awssdk_eventstream_version=1.0.1 From fa875b69467b9d8e25295114689beb2fd6856939 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 21:09:03 +0800 Subject: [PATCH 37/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0token=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/impl/SelfHost.java | 3 ++- .../pack/host/impl/SelfHostHttpServer.java | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index fb0a1223c..9febdb22e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -67,6 +67,7 @@ public class SelfHost implements ResourcePackHost { if (port < 0 || port > 65535) { throw new IllegalArgumentException("Illegal port: '" + port + "' for self host"); } + boolean oneTimeToken = (boolean) arguments.getOrDefault("one-time-token", true); String protocol = (String) arguments.getOrDefault("protocol", "http"); boolean denyNonMinecraftRequest = (boolean) arguments.getOrDefault("deny-non-minecraft-request", true); Map rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true); @@ -76,7 +77,7 @@ public class SelfHost implements ResourcePackHost { maxRequests = (int) rateMap.getOrDefault("max-requests", 5); resetInterval = (int) rateMap.getOrDefault("reset-interval", 20) * 1000; } - selfHostHttpServer.updateProperties(ip, port, denyNonMinecraftRequest, protocol, maxRequests, resetInterval); + selfHostHttpServer.updateProperties(ip, port, denyNonMinecraftRequest, protocol, maxRequests, resetInterval, oneTimeToken); return INSTANCE; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index cd1666c1f..0a08193e6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -49,6 +49,7 @@ public class SelfHostHttpServer { private int port = -1; private String protocol = "http"; private boolean denyNonMinecraft = true; + private boolean useToken; private volatile byte[] resourcePackBytes; private String packHash; @@ -59,12 +60,14 @@ public class SelfHostHttpServer { boolean denyNonMinecraft, String protocol, int maxRequests, - int resetInternal) { + int resetInternal, + boolean token) { this.ip = ip; this.denyNonMinecraft = denyNonMinecraft; this.protocol = protocol; this.rateLimit = maxRequests; this.rateLimitInterval = resetInternal; + this.useToken = token; if (port <= 0 || port > 65535) { throw new IllegalArgumentException("Invalid port number: " + port); } @@ -96,6 +99,9 @@ public class SelfHostHttpServer { if (this.resourcePackBytes == null) { return null; } + if (!this.useToken) { + return new ResourcePackDownloadData(url(), packUUID, packHash); + } String token = UUID.randomUUID().toString(); this.oneTimePackUrls.put(token, true); return new ResourcePackDownloadData( @@ -172,10 +178,12 @@ public class SelfHostHttpServer { handleBlockedRequest(exchange, 429, "Rate limit exceeded"); return; } - String token = parseToken(exchange); - if (!validateToken(token)) { - handleBlockedRequest(exchange, 403, "Invalid token"); - return; + if (useToken) { + String token = parseToken(exchange); + if (!validateToken(token)) { + handleBlockedRequest(exchange, 403, "Invalid token"); + return; + } } if (!validateClient(exchange)) { handleBlockedRequest(exchange, 403, "Invalid client"); From 8244f13fe26808597a7f946243320f4e7e177c60 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 21:09:41 +0800 Subject: [PATCH 38/62] Update config.yml --- bukkit/loader/src/main/resources/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index d9fc6deed..91ef6eea5 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -79,6 +79,7 @@ resource-pack: port: 8163 protocol: "http" deny-non-minecraft-request: true + one-time-token: true rate-limit: max-requests: 3 reset-interval: 20 From 24aec399cb33aa8d60cc6c1f3003298fb9c436a6 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 18 Apr 2025 21:19:35 +0800 Subject: [PATCH 39/62] Update SelfHostHttpServer.java --- .../craftengine/core/pack/host/impl/SelfHostHttpServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 0a08193e6..9fc35867e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -100,7 +100,7 @@ public class SelfHostHttpServer { return null; } if (!this.useToken) { - return new ResourcePackDownloadData(url(), packUUID, packHash); + return new ResourcePackDownloadData(url() + "download", packUUID, packHash); } String token = UUID.randomUUID().toString(); this.oneTimePackUrls.put(token, true); From 330819d695c1b69f68d79d9c1ba09f68446edbac Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 18 Apr 2025 22:57:56 +0800 Subject: [PATCH 40/62] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0=20alist?= =?UTF-8?q?=20=E6=89=98=E7=AE=A1=E4=B8=8A=E4=BC=A0=E7=A6=81=E7=94=A8?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 用于兼容 alist 一些不支持上传方法的存储方案 - 即使禁用了也要使用上传命令清理缓存 --- .../core/pack/host/impl/AlistHost.java | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 19ebe3055..cbbc41327 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -24,6 +24,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -37,6 +39,7 @@ public class AlistHost implements ResourcePackHost { private final String otpCode; private final Duration jwtTokenExpiration; private final String uploadPath; + private final boolean disableUpload; private final ProxySelector proxy; private Pair jwtToken; private String cachedSha1; @@ -48,6 +51,7 @@ public class AlistHost implements ResourcePackHost { String otpCode, Duration jwtTokenExpiration, String uploadPath, + boolean disableUpload, ProxySelector proxy) { this.apiUrl = apiUrl; this.userName = userName; @@ -56,6 +60,7 @@ public class AlistHost implements ResourcePackHost { this.otpCode = otpCode; this.jwtTokenExpiration = jwtTokenExpiration; this.uploadPath = uploadPath; + this.disableUpload = disableUpload; this.proxy = proxy; this.readCacheFromDisk(); } @@ -132,6 +137,11 @@ public class AlistHost implements ResourcePackHost { @Override public CompletableFuture upload(Path resourcePackPath) { + if (this.disableUpload) { + this.cachedSha1 = ""; + saveCacheToDisk(); + return CompletableFuture.completedFuture(null); + } CompletableFuture future = new CompletableFuture<>(); CraftEngine.instance().scheduler().executeAsync(() -> { try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { @@ -239,6 +249,32 @@ public class AlistHost implements ResourcePackHost { boolean isDir = dataObj.getAsJsonPrimitive("is_dir").getAsBoolean(); if (!isDir) { String url = dataObj.getAsJsonPrimitive("raw_url").getAsString(); + if ((this.cachedSha1 == null || this.cachedSha1.isEmpty()) && this.disableUpload) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .GET() + .build(); + HttpResponse responseHash = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + try (InputStream inputStream = responseHash.body()) { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] buffer = new byte[8192]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + md.update(buffer, 0, len); + } + byte[] digest = md.digest(); + this.cachedSha1 = HexFormat.of().formatHex(digest); + saveCacheToDisk(); + } catch (NoSuchAlgorithmException e) { + future.completeExceptionally(new RuntimeException("Failed to calculate SHA-1 hash algorithm", e)); + return; + } + } catch (IOException | InterruptedException e) { + future.completeExceptionally(new RuntimeException("Failed to retrieve remote resource pack for hashing", e)); + return; + } + } UUID uuid = UUID.nameUUIDFromBytes(Objects.requireNonNull(this.cachedSha1).getBytes(StandardCharsets.UTF_8)); future.complete(List.of(new ResourcePackDownloadData(url, uuid, this.cachedSha1))); return; @@ -273,8 +309,9 @@ public class AlistHost implements ResourcePackHost { if (uploadPath == null || uploadPath.isEmpty()) { throw new IllegalArgumentException("'upload-path' cannot be empty for Alist host"); } + boolean disableUpload = (boolean) arguments.getOrDefault("disable-upload", false); ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, uploadPath, proxy); + return new AlistHost(apiUrl, userName, password, filePassword, otpCode, jwtTokenExpiration, uploadPath, disableUpload, proxy); } } } \ No newline at end of file From 6a1c5249632974e8143a597b8deac5f8c114680f Mon Sep 17 00:00:00 2001 From: iqtester Date: Fri, 18 Apr 2025 23:42:39 +0800 Subject: [PATCH 41/62] feat: Add Block Properties Suggestion to WE & DebugSetBlockCommand --- .../worldedit/WorldEditBlockRegister.java | 32 +-- .../bukkit/block/BukkitBlockManager.java | 16 +- .../command/feature/DebugSetBlockCommand.java | 3 +- .../core/block/BlockStateParser.java | 187 +++++++++++++++++- 4 files changed, 198 insertions(+), 40 deletions(-) diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java index 3f3766162..371262dec 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java @@ -16,46 +16,29 @@ import net.momirealms.craftengine.core.util.ReflectionUtils; import org.bukkit.Material; import java.lang.reflect.Field; -import java.util.Collection; -import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; public class WorldEditBlockRegister { private final Field field$BlockType$blockMaterial; private final AbstractBlockManager manager; - private final Set cachedSuggestions = new HashSet<>(); public WorldEditBlockRegister(AbstractBlockManager manager) { field$BlockType$blockMaterial = ReflectionUtils.getDeclaredField(BlockType.class, "blockMaterial"); this.manager = manager; - } - - public void enable() { CEBlockParser blockParser = new CEBlockParser(WorldEdit.getInstance()); WorldEdit.getInstance().getBlockFactory().register(blockParser); } - public void load() { - Collection cachedSuggestions = manager.cachedSuggestions(); - for (Object o : cachedSuggestions) { - this.cachedSuggestions.add(o.toString()); - } - } - - public void unload() { - cachedSuggestions.clear(); - } - public void register(Key id) throws ReflectiveOperationException { BlockType blockType = new BlockType(id.toString(), blockState -> blockState); field$BlockType$blockMaterial.set(blockType, LazyReference.from(() -> new BukkitBlockRegistry.BukkitBlockMaterial(null, Material.STONE))); BlockType.REGISTRY.register(id.toString(), blockType); } - private class CEBlockParser extends InputParser { + private final class CEBlockParser extends InputParser { - protected CEBlockParser(WorldEdit worldEdit) { + private CEBlockParser(WorldEdit worldEdit) { super(worldEdit); } @@ -68,22 +51,25 @@ public class WorldEditBlockRegister { } if (input.startsWith(":")) { - String term = input.substring(1).toLowerCase(); - return cachedSuggestions.stream().filter(s -> s.toLowerCase().contains(term)); + String term = input.substring(1); + return BlockStateParser.fillSuggestions(term).stream(); } if (!input.contains(":")) { String lowerSearch = input.toLowerCase(); return Stream.concat( namespacesInUse.stream().filter(n -> n.startsWith(lowerSearch)).map(n -> n + ":"), - cachedSuggestions.stream().filter(s -> s.toLowerCase().startsWith(lowerSearch)) + BlockStateParser.fillSuggestions(input).stream() ); } - return cachedSuggestions.stream().filter(s -> s.toLowerCase().startsWith(input.toLowerCase())); + return BlockStateParser.fillSuggestions(input).stream(); } @Override public BaseBlock parseFromInput(String input, ParserContext context) { + int index = input.indexOf("["); + if (input.charAt(index+1) == ']') return null; + int colonIndex = input.indexOf(':'); if (colonIndex == -1) return null; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 259629544..51dd78971 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -87,8 +87,6 @@ public class BukkitBlockManager extends AbstractBlockManager { // Event listeners private final BlockEventListener blockEventListener; private final FallingBlockRemoveListener fallingBlockRemoveListener; - // WE support - private WorldEditBlockRegister weBlockRegister; public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin); @@ -146,8 +144,6 @@ public class BukkitBlockManager extends AbstractBlockManager { this.modBlockStates.clear(); if (EmptyBlock.INSTANCE != null) Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.defaultState()); - if (weBlockRegister != null) - weBlockRegister.unload(); } @Override @@ -167,7 +163,6 @@ public class BukkitBlockManager extends AbstractBlockManager { initSuggestions(); resetPacketConsumers(); clearCache(); - loadWorldEditRegister(); } private void clearCache() { @@ -177,13 +172,11 @@ public class BukkitBlockManager extends AbstractBlockManager { } public void initFastAsyncWorldEditHook() { - this.weBlockRegister = new WorldEditBlockRegister(this); - this.weBlockRegister.enable(); + new WorldEditBlockRegister(this); } public void initWorldEditHook() { - this.weBlockRegister = new WorldEditBlockRegister(this); - this.weBlockRegister.enable(); + WorldEditBlockRegister weBlockRegister = new WorldEditBlockRegister(this); try { for (Key newBlockId : this.blockRegisterOrder) { weBlockRegister.register(newBlockId); @@ -193,11 +186,6 @@ public class BukkitBlockManager extends AbstractBlockManager { } } - public void loadWorldEditRegister() { - if (this.weBlockRegister != null) - this.weBlockRegister.load(); - } - @Nullable public Object getMinecraftBlockHolder(int stateId) { return stateId2BlockHolder.get(stateId); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSetBlockCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSetBlockCommand.java index 80cf55d91..58ca767dc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSetBlockCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugSetBlockCommand.java @@ -19,6 +19,7 @@ import org.incendo.cloud.suggestion.Suggestion; import org.incendo.cloud.suggestion.SuggestionProvider; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; public class DebugSetBlockCommand extends BukkitCommandFeature { @@ -33,7 +34,7 @@ public class DebugSetBlockCommand extends BukkitCommandFeature { .required("id", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).suggestionProvider(new SuggestionProvider<>() { @Override public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { - return CompletableFuture.completedFuture(plugin().blockManager().cachedSuggestions()); + return CompletableFuture.completedFuture(BlockStateParser.fillSuggestions(input.input(), input.cursor()).stream().map(Suggestion::suggestion).collect(Collectors.toList())); } })) .handler(context -> { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateParser.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateParser.java index 8f1837886..f8cc31c6e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateParser.java @@ -8,9 +8,183 @@ import net.momirealms.craftengine.core.util.StringReader; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; public class BlockStateParser { + private static final char START = '['; + private static final char EQUAL = '='; + private static final char SEPARATOR = ','; + private static final char END = ']'; + + private final StringReader reader; + private final int cursor; + private final Set suggestions = new HashSet<>(); + private final Set used = new HashSet<>(); + + private String input; + private int replaceCursor; + private Holder block; + private Collection> properties; + private Property property; + + public BlockStateParser(String data, int cursor) { + this.reader = new StringReader(data.toLowerCase()); + this.reader.setCursor(cursor); + this.cursor = cursor; + this.replaceCursor = cursor; + } + + public static Set fillSuggestions(@NotNull String data) { + return fillSuggestions(data, 0); + } + + public static Set fillSuggestions(@NotNull String data, int cursor) { + BlockStateParser parser = new BlockStateParser(data, cursor); + parser.parse(); + return parser.suggestions; + } + + private void parse() { + readBlock(); + if (block == null) { + suggestBlock(); + return; + } + + readProperties(); + if (properties.isEmpty()) return; + + if (!reader.canRead()) + suggestStart(); + else if (reader.peek() == START) { + reader.skip(); + suggestProperties(); + } + } + + private void readBlock() { + this.replaceCursor = reader.getCursor(); + this.input = reader.readUnquotedString(); + if (reader.canRead() && reader.peek() == ':') { + reader.skip(); + input = input + ":" + reader.readUnquotedString(); + } + BuiltInRegistries.BLOCK.get(Key.from(input)).ifPresent(block -> this.block = block); + } + + private void suggestBlock() { + String front = readPrefix(); + for (Key key : BuiltInRegistries.BLOCK.keySet()) { + String id = key.toString(); + if (id.contains(input)) { + this.suggestions.add(front + id); + } + } + this.suggestions.remove(front + "craftengine:empty"); + } + + private void readProperties() { + this.properties = this.block.value().properties(); + } + + private void suggestStart() { + this.replaceCursor = reader.getCursor(); + this.suggestions.add(readPrefix() + START); + } + + private void suggestProperties() { + this.reader.skipWhitespace(); + this.replaceCursor = reader.getCursor(); + suggestPropertyNameAndEnd(); + + while (reader.canRead()) { + if (used.isEmpty() && reader.peek() == SEPARATOR) return; + if (reader.peek() == SEPARATOR) reader.skip(); + reader.skipWhitespace(); + if (reader.canRead() && reader.peek() == END) return; + + replaceCursor = reader.getCursor(); + input = reader.readString(); + + property = block.value().getProperty(input); + if (property == null) { + suggestPropertyName(); + return; + } + if (used.contains(property.name().toLowerCase())) return; + used.add(input); + + reader.skipWhitespace(); + replaceCursor = reader.getCursor(); + suggestEqual(); + + if (!reader.canRead() || reader.peek() != EQUAL) return; + + reader.skip(); + reader.skipWhitespace(); + replaceCursor = reader.getCursor(); + input = reader.readString(); + if (property.possibleValues().stream().noneMatch + (value -> value.toString().equalsIgnoreCase(input)) + ){ + suggestValue(); + return; + } + + reader.skipWhitespace(); + replaceCursor = reader.getCursor(); + if (reader.canRead()) { + if (used.size() == properties.size()) return; + if (reader.peek() != SEPARATOR) return; + } else if (used.size() < properties.size()) { + suggestSeparator(); + } + } + suggestEnd(); + } + + private void suggestPropertyNameAndEnd() { + if (!reader.getRemaining().isEmpty()) return; + this.input = ""; + suggestEnd(); + suggestPropertyName(); + + } + private void suggestPropertyName() { + if (!reader.getRemaining().isEmpty()) return; + String front = readPrefix(); + for (Property p : properties) { + if (!used.contains(p.name().toLowerCase()) && p.name().toLowerCase().startsWith(input)) { + this.suggestions.add(front + p.name() + EQUAL); + } + } + } + + private void suggestEqual() { + if (!reader.getRemaining().isEmpty()) return; + this.suggestions.add(readPrefix() + EQUAL); + } + + private void suggestValue() { + for (Object val : property.possibleValues()) { + this.suggestions.add(readPrefix() + val.toString().toLowerCase()); + } + } + + private void suggestSeparator() { + this.suggestions.add(readPrefix() + SEPARATOR); + } + + private void suggestEnd() { + this.suggestions.add(readPrefix() + END); + } + + private String readPrefix() { + return reader.getString().substring(cursor, replaceCursor); + } @Nullable public static ImmutableBlockState deserialize(@NotNull String data) { @@ -28,26 +202,35 @@ public class BlockStateParser { ImmutableBlockState defaultState = holder.value().defaultState(); if (reader.canRead() && reader.peek() == '[') { reader.skip(); - while (reader.canRead() && reader.peek() != ']') { + while (reader.canRead()) { + reader.skipWhitespace(); + if (reader.peek() == ']') break; String propertyName = reader.readUnquotedString(); + reader.skipWhitespace(); if (!reader.canRead() || reader.peek() != '=') { return null; } reader.skip(); + reader.skipWhitespace(); String propertyValue = reader.readUnquotedString(); Property property = holder.value().getProperty(propertyName); if (property != null) { Optional optionalValue = property.optional(propertyValue); if (optionalValue.isEmpty()) { - defaultState = ImmutableBlockState.with(defaultState, property, property.defaultValue()); + //defaultState = ImmutableBlockState.with(defaultState, property, property.defaultValue()); + return null; } else { defaultState = ImmutableBlockState.with(defaultState, property, optionalValue.get()); } + } else { + return null; } + reader.skipWhitespace(); if (reader.canRead() && reader.peek() == ',') { reader.skip(); } } + reader.skipWhitespace(); if (reader.canRead() && reader.peek() == ']') { reader.skip(); } else { From 02dc5bbb21b85e5a62ec4f3d9ae73e2fc5830818 Mon Sep 17 00:00:00 2001 From: iqtester Date: Sat, 19 Apr 2025 02:17:36 +0800 Subject: [PATCH 42/62] fix: WE Suggestion --- .../worldedit/WorldEditBlockRegister.java | 10 +++++++--- .../craftengine/bukkit/block/BukkitBlockManager.java | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java index 371262dec..94c28ebea 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/WorldEditBlockRegister.java @@ -22,10 +22,12 @@ import java.util.stream.Stream; public class WorldEditBlockRegister { private final Field field$BlockType$blockMaterial; private final AbstractBlockManager manager; + private final boolean isFAWE; - public WorldEditBlockRegister(AbstractBlockManager manager) { + public WorldEditBlockRegister(AbstractBlockManager manager, boolean isFAWE) { field$BlockType$blockMaterial = ReflectionUtils.getDeclaredField(BlockType.class, "blockMaterial"); this.manager = manager; + this.isFAWE = isFAWE; CEBlockParser blockParser = new CEBlockParser(WorldEdit.getInstance()); WorldEdit.getInstance().getBlockFactory().register(blockParser); } @@ -67,8 +69,10 @@ public class WorldEditBlockRegister { @Override public BaseBlock parseFromInput(String input, ParserContext context) { - int index = input.indexOf("["); - if (input.charAt(index+1) == ']') return null; + if (isFAWE) { + int index = input.indexOf("["); + if (input.charAt(index+1) == ']') return null; + } int colonIndex = input.indexOf(':'); if (colonIndex == -1) return null; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 51dd78971..57d41aabe 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -172,11 +172,11 @@ public class BukkitBlockManager extends AbstractBlockManager { } public void initFastAsyncWorldEditHook() { - new WorldEditBlockRegister(this); + new WorldEditBlockRegister(this, true); } public void initWorldEditHook() { - WorldEditBlockRegister weBlockRegister = new WorldEditBlockRegister(this); + WorldEditBlockRegister weBlockRegister = new WorldEditBlockRegister(this, false); try { for (Key newBlockId : this.blockRegisterOrder) { weBlockRegister.register(newBlockId); From 13721bed7c4ab34c4f7a2991a6c00409c03d702f Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 03:51:07 +0800 Subject: [PATCH 43/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8D1.20.1=E9=AA=A8?= =?UTF-8?q?=E7=B2=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/behavior/CropBlockBehavior.java | 2 +- .../bukkit/block/behavior/SaplingBlockBehavior.java | 2 +- .../bukkit/item/behavior/BoneMealItemBehavior.java | 4 +++- .../bukkit/plugin/network/BukkitNetworkManager.java | 1 + gradle.properties | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java index 4dd7721ce..9a97404e7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java @@ -134,7 +134,7 @@ public class CropBlockBehavior extends BushBlockBehavior { Object visualState = immutableBlockState.vanillaBlockState().handle(); Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { - boolean is = (boolean) Reflections.method$BonemealableBlock$isValidBonemealTarget.invoke(visualStateBlock, level, pos, visualState); + boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, pos, visualState); if (!is) { sendParticles = true; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java index 5bfeab461..eed9879f0 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java @@ -108,7 +108,7 @@ public class SaplingBlockBehavior extends BushBlockBehavior { Object visualState = immutableBlockState.vanillaBlockState().handle(); Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { - boolean is = (boolean) Reflections.method$BonemealableBlock$isValidBonemealTarget.invoke(visualStateBlock, level, blockPos, visualState); + boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState); if (!is) { sendParticles = true; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java index fcac2aa5c..85b629dca 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.bukkit.item.behavior; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.block.behavior.CropBlockBehavior; import net.momirealms.craftengine.bukkit.block.behavior.SaplingBlockBehavior; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.Reflections; @@ -15,6 +16,7 @@ import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.block.Block; import java.nio.file.Path; @@ -51,7 +53,7 @@ public class BoneMealItemBehavior extends ItemBehavior { Object visualState = state.vanillaBlockState().handle(); Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { - boolean is = (boolean) Reflections.method$BonemealableBlock$isValidBonemealTarget.invoke(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); + boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); if (!is) { sendSwing = true; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index afe8aecd1..c5160f6db 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -39,6 +39,7 @@ import java.util.function.BiConsumer; public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener { private static BukkitNetworkManager instance; private static final Map, TriConsumer> NMS_PACKET_HANDLERS = new HashMap<>(); + // only for game stage for the moment private static final Map> BYTE_BUFFER_PACKET_HANDLERS = new HashMap<>(); private static void registerNMSPacketConsumer(final TriConsumer function, @Nullable Class packet) { diff --git a/gradle.properties b/gradle.properties index f7f5042e2..60902ac30 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.49-beta.2 +project_version=0.0.49-beta.3 config_version=28 lang_version=5 project_group=net.momirealms @@ -51,7 +51,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.13 -nms_helper_version=0.59.5 +nms_helper_version=0.59.6 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 amazon_awssdk_eventstream_version=1.0.1 From afef0c80fa5d931d3c12d0ef8e13a5afc9814151 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 07:34:13 +0800 Subject: [PATCH 44/62] =?UTF-8?q?fix(s3):=20=E5=9B=9E=E9=80=80=E4=B9=8B?= =?UTF-8?q?=E5=89=8D=E5=88=A0=E9=99=A4=E4=BE=9D=E8=B5=96=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../momirealms/craftengine/core/plugin/CraftEngine.java | 1 + .../craftengine/core/plugin/dependency/Dependencies.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 974e9c294..e7de1ecf4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -292,6 +292,7 @@ public abstract class CraftEngine implements Plugin { Dependencies.TEXT_SERIALIZER_JSON, Dependencies.AHO_CORASICK, Dependencies.LZ4, + Dependencies.NETTY_HTTP, Dependencies.NETTY_HTTP2, Dependencies.REACTIVE_STREAMS, Dependencies.AMAZON_AWSSDK_S3, diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index e9861fc2a..1859a2847 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -248,6 +248,14 @@ public class Dependencies { List.of(Relocation.of("jpountz", "net{}jpountz")) ); + public static final Dependency NETTY_HTTP = new Dependency( + "netty-codec-http", + "io{}netty", + "netty-codec-http", + "netty-codec-http", + Collections.emptyList() + ); + public static final Dependency NETTY_HTTP2 = new Dependency( "netty-codec-http2", "io{}netty", From b94c0ec687692ea14b70866fee7049c5d18ff508 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 08:48:38 +0800 Subject: [PATCH 45/62] =?UTF-8?q?feat(core):=20=E6=9B=B4=E6=96=B0=20Dropbo?= =?UTF-8?q?x=20=E6=89=98=E7=AE=A1=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 之前的会过期使用新方法防止过期 --- .../core/pack/host/impl/DropboxHost.java | 317 ++++++++++-------- 1 file changed, 185 insertions(+), 132 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java index a3f3edafe..dc13db502 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -1,46 +1,36 @@ package net.momirealms.craftengine.core.pack.host.impl; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; -import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; -import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; -import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; +import com.google.gson.*; +import net.momirealms.craftengine.core.pack.host.*; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.GsonHelper; -import net.momirealms.craftengine.core.util.HashUtils; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.ProxySelector; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; +import net.momirealms.craftengine.core.util.*; +import java.io.*; +import java.net.*; +import java.net.http.*; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; +import java.nio.file.*; import java.util.*; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.*; +import java.util.concurrent.locks.ReentrantLock; public class DropboxHost implements ResourcePackHost { public static final Factory FACTORY = new Factory(); - private final String accessToken; + private final String appKey; + private final String appSecret; private final String uploadPath; private final ProxySelector proxy; + private final ReentrantLock tokenLock = new ReentrantLock(); + private volatile String accessToken; + private volatile String refreshToken; + private volatile long expiresAt; private String url; private String sha1; - private UUID uuid; - public DropboxHost(String accessToken, String uploadPath, ProxySelector proxy) { - this.accessToken = accessToken; + public DropboxHost(String appKey, String appSecret, String refreshToken, String uploadPath, ProxySelector proxy) { + this.appKey = appKey; + this.appSecret = appSecret; + this.refreshToken = refreshToken; this.uploadPath = uploadPath; this.proxy = proxy; readCacheFromDisk(); @@ -51,31 +41,27 @@ public class DropboxHost implements ResourcePackHost { if (!Files.exists(cachePath)) return; try (InputStream is = Files.newInputStream(cachePath)) { - Map cache = GsonHelper.get().fromJson( - new InputStreamReader(is), - new TypeToken>(){}.getType() - ); + JsonObject cache = GsonHelper.parseJsonToJsonObject(new String(is.readAllBytes(), StandardCharsets.UTF_8)); - this.url = cache.get("url"); - this.sha1 = cache.get("sha1"); - - String uuidString = cache.get("uuid"); - if (uuidString != null && !uuidString.isEmpty()) { - this.uuid = UUID.fromString(uuidString); - } + this.url = getString(cache, "url"); + this.sha1 = getString(cache, "sha1"); + this.refreshToken = getString(cache, "refresh_token"); + this.accessToken = getString(cache, "access_token"); + this.expiresAt = getLong(cache, "expires_at"); CraftEngine.instance().logger().info("[Dropbox] Loaded cached resource pack info"); } catch (Exception e) { - CraftEngine.instance().logger().warn( - "[Dropbox] Failed to load cache from disk: " + e.getMessage()); + CraftEngine.instance().logger().warn("[Dropbox] Failed to load cache", e); } } public void saveCacheToDisk() { - Map cache = new HashMap<>(); - cache.put("url", this.url); - cache.put("sha1", this.sha1); - cache.put("uuid", this.uuid != null ? this.uuid.toString() : ""); + JsonObject cache = new JsonObject(); + cache.addProperty("url", this.url); + cache.addProperty("sha1", this.sha1); + cache.addProperty("refresh_token", this.refreshToken); + cache.addProperty("access_token", this.accessToken); + cache.addProperty("expires_at", this.expiresAt); Path cachePath = CraftEngine.instance().dataFolderPath().resolve("dropbox.cache"); try { @@ -86,8 +72,7 @@ public class DropboxHost implements ResourcePackHost { StandardOpenOption.TRUNCATE_EXISTING ); } catch (IOException e) { - CraftEngine.instance().logger().warn( - "[Dropbox] Failed to persist cache to disk: " + e.getMessage()); + CraftEngine.instance().logger().warn("[Dropbox] Failed to save cache", e); } } @@ -103,53 +88,58 @@ public class DropboxHost implements ResourcePackHost { @Override public CompletableFuture> requestResourcePackDownloadLink(UUID player) { - if (this.url == null) return CompletableFuture.completedFuture(Collections.emptyList()); - return CompletableFuture.completedFuture(List.of(ResourcePackDownloadData.of(this.url, this.uuid, this.sha1))); + return CompletableFuture.completedFuture(Collections.singletonList(ResourcePackDownloadData.of( + this.url, UUID.nameUUIDFromBytes(this.sha1.getBytes(StandardCharsets.UTF_8)), this.sha1 + ))); } @Override public CompletableFuture upload(Path resourcePackPath) { CompletableFuture future = new CompletableFuture<>(); - CraftEngine.instance().scheduler().executeAsync(() -> { - String sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); - try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { - JsonObject apiArg = new JsonObject(); - apiArg.addProperty("path", this.uploadPath); - apiArg.addProperty("mode", "overwrite"); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://content.dropboxapi.com/2/files/upload")) - .header("Authorization", "Bearer " + this.accessToken) - .header("Content-Type", "application/octet-stream") - .header("Dropbox-API-Arg", apiArg.toString()) - .POST(HttpRequest.BodyPublishers.ofFile(resourcePackPath)) - .build(); - long uploadStart = System.currentTimeMillis(); - CraftEngine.instance().logger().info("[Dropbox] Initiating resource pack upload"); - client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenAccept(response -> { - long uploadTime = System.currentTimeMillis() - uploadStart; - CraftEngine.instance().logger().info( - "[Dropbox] Upload completed in " + uploadTime + " ms"); - if (response.statusCode() == 200) { - this.sha1 = sha1; - this.uuid = UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)); - this.url = getDownloadUrl(); - saveCacheToDisk(); - future.complete(null); - return; - } - CraftEngine.instance().logger().warn( - "[Dropbox] Upload failed (HTTP " + response.statusCode() + "): " + response.body()); - future.completeExceptionally(new RuntimeException(response.body())); - }) - .exceptionally(ex -> { - CraftEngine.instance().logger().warn("[Dropbox] Upload operation failed: " + ex.getMessage()); - future.completeExceptionally(ex); - return null; - }); - } catch (FileNotFoundException e) { - CraftEngine.instance().logger().warn("[Dropbox] Resource pack file not found: " + e.getMessage()); + CraftEngine.instance().scheduler().executeAsync(() -> { + try { + String validToken = getOrRefreshToken(); + this.sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); + + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { + JsonObject apiArg = new JsonObject(); + apiArg.addProperty("path", this.uploadPath); + apiArg.addProperty("mode", "overwrite"); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://content.dropboxapi.com/2/files/upload")) + .header("Authorization", "Bearer " + validToken) + .header("Content-Type", "application/octet-stream") + .header("Dropbox-API-Arg", apiArg.toString()) + .POST(HttpRequest.BodyPublishers.ofFile(resourcePackPath)) + .build(); + + long startTime = System.currentTimeMillis(); + CraftEngine.instance().logger().info("[Dropbox] Starting upload..."); + + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + long elapsed = System.currentTimeMillis() - startTime; + if (response.statusCode() == 200) { + CraftEngine.instance().logger().info( + "[Dropbox] Upload completed in " + elapsed + "ms"); + this.url = getDownloadUrl(validToken); + saveCacheToDisk(); + future.complete(null); + } else { + CraftEngine.instance().logger().warn( + "[Dropbox] Upload failed (HTTP " + response.statusCode() + "): " + response.body()); + future.completeExceptionally(new RuntimeException(response.body())); + } + }) + .exceptionally(ex -> { + CraftEngine.instance().logger().warn("[Dropbox] Upload error", ex); + future.completeExceptionally(ex); + return null; + }); + } + } catch (Exception e) { future.completeExceptionally(e); } }); @@ -157,62 +147,125 @@ public class DropboxHost implements ResourcePackHost { return future; } - private String getDownloadUrl() { + private String getDownloadUrl(String accessToken) { try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { - try { - JsonObject requestJson = new JsonObject(); - requestJson.addProperty("path", this.uploadPath); - JsonObject settingsJson = new JsonObject(); - settingsJson.addProperty("requested_visibility", "public"); - requestJson.add("settings", settingsJson); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings")) - .header("Authorization", "Bearer " + this.accessToken) + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("path", this.uploadPath); + requestBody.add("settings", new JsonObject()); + requestBody.getAsJsonObject("settings").addProperty("requested_visibility", "public"); + + HttpRequest createLinkRequest = HttpRequest.newBuilder() + .uri(URI.create("https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings")) + .header("Authorization", "Bearer " + accessToken) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())) + .build(); + + HttpResponse response = client.send(createLinkRequest, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 409) { + JsonObject listRequest = new JsonObject(); + listRequest.addProperty("path", this.uploadPath); + + HttpRequest listLinksRequest = HttpRequest.newBuilder() + .uri(URI.create("https://api.dropboxapi.com/2/sharing/list_shared_links")) + .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(requestJson.toString())) + .POST(HttpRequest.BodyPublishers.ofString(listRequest.toString())) .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == 409) { - JsonObject listJson = new JsonObject(); - listJson.addProperty("path", this.uploadPath); - HttpRequest listLinksRequest = HttpRequest.newBuilder() - .uri(URI.create("https://api.dropboxapi.com/2/sharing/list_shared_links")) - .header("Authorization", "Bearer " + this.accessToken) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(listJson.toString())) - .build(); - HttpResponse listResponse = client.send(listLinksRequest, HttpResponse.BodyHandlers.ofString()); - if (listResponse.statusCode() == 200) { - JsonObject responseJson = GsonHelper.parseJsonToJsonObject(listResponse.body()); - JsonArray links = responseJson.getAsJsonArray("links"); - if (!links.isEmpty()) { - return links.get(0).getAsJsonObject().get("url").getAsString().replace("dl=0", "dl=1"); - } - } - } else if (response.statusCode() != 200) { - CraftEngine.instance().logger().warn("[Dropbox] Failed to retrieve download URL (HTTP " + response.statusCode() + "): " + response.body()); - return null; + + HttpResponse listResponse = client.send(listLinksRequest, HttpResponse.BodyHandlers.ofString()); + JsonObject listData = GsonHelper.parseJsonToJsonObject(listResponse.body()); + JsonArray links = listData.getAsJsonArray("links"); + if (!links.isEmpty()) { + return links.get(0).getAsJsonObject().get("url").getAsString().replace("dl=0", "dl=1"); } - JsonObject jsonData = GsonHelper.parseJsonToJsonObject(response.body()); - return jsonData.getAsJsonPrimitive("url").getAsString().replace("dl=0", "dl=1"); - } catch (IOException | InterruptedException e) { - CraftEngine.instance().logger().warn("[Dropbox] Error generating download link: " + e.getMessage()); - return null; } + + JsonObject responseData = GsonHelper.parseJsonToJsonObject(response.body()); + return responseData.get("url").getAsString().replace("dl=0", "dl=1"); + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Failed to get download URL", e); + } + } + + private String getString(JsonObject json, String key) { + return json.has(key) ? json.get(key).getAsString() : null; + } + + @SuppressWarnings("SameParameterValue") + private long getLong(JsonObject json, String key) { + return json.has(key) ? json.get(key).getAsLong() : 0; + } + + private String getOrRefreshToken() { + if (System.currentTimeMillis() < expiresAt - 30000 && this.accessToken != null) { + return this.accessToken; + } + this.tokenLock.lock(); + try { + if (System.currentTimeMillis() < expiresAt - 30000 && this.accessToken != null) { + return this.accessToken; + } + + String credentials = this.appKey + ":" + this.appSecret; + String authHeader = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes()); + + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://alist.nn.ci/tool/dropbox/callback")) + .header("Content-Type", "application/x-www-form-urlencoded") + .header("Authorization", authHeader) + .POST(HttpRequest.BodyPublishers.ofString( + "grant_type=refresh_token" + + "&refresh_token=" + this.refreshToken + )) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 200) { + throw new RuntimeException("Refresh failed: " + response.body()); + } + + JsonObject tokenData = GsonHelper.parseJsonToJsonObject(response.body()); + this.accessToken = tokenData.get("access_token").getAsString(); + this.expiresAt = System.currentTimeMillis() + + tokenData.get("expires_in").getAsLong() * 1000; + + if (tokenData.has("refresh_token")) { + this.refreshToken = tokenData.get("refresh_token").getAsString(); + } + + saveCacheToDisk(); + return this.accessToken; + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Token refresh failed", e); + } + } finally { + this.tokenLock.unlock(); } } public static class Factory implements ResourcePackHostFactory { - @Override public ResourcePackHost create(Map arguments) { - String accessToken = (String) arguments.get("access-token"); - if (accessToken == null || accessToken.isEmpty()) { - throw new IllegalArgumentException("Missing required 'access-token' configuration"); + String appKey = (String) arguments.get("app-key"); + if (appKey == null || appKey.isEmpty()) { + throw new IllegalArgumentException("Missing required 'app-key' configuration"); } - String uploadPath = (String) arguments.getOrDefault("upload-path", "/resource_pack.zip"); + String appSecret = (String) arguments.get("app-secret"); + if (appSecret == null || appSecret.isEmpty()) { + throw new IllegalArgumentException("Missing required 'app-secret' configuration"); + } + String refreshToken = (String) arguments.get("refresh-token"); + if (refreshToken == null || refreshToken.isEmpty()) { + throw new IllegalArgumentException("Missing required 'refresh-token' configuration"); + } + String uploadPath = (String) arguments.getOrDefault("upload-path", "resource_pack.zip"); + ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); - return new DropboxHost(accessToken, "/" + uploadPath, proxy); + return new DropboxHost(appKey, appSecret, refreshToken, "/" + uploadPath, proxy); } } } \ No newline at end of file From f8adea4a55c3d7acb65dc5587696379860c74db2 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 12:17:03 +0800 Subject: [PATCH 46/62] =?UTF-8?q?feat(core):=20=E4=BF=AE=E5=A4=8D=E6=96=B0?= =?UTF-8?q?=E7=89=88=E6=9C=ACjdk=E8=BE=93=E5=87=BA=E7=9A=84=E5=8E=8B?= =?UTF-8?q?=E7=BC=A9=E5=8C=85=E6=9C=89=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/momirealms/craftengine/core/pack/obfuscation/ObfD.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ObfD.java b/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ObfD.java index ee8eceb34..991136116 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ObfD.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/obfuscation/ObfD.java @@ -130,6 +130,7 @@ public final class ObfD { long 原始大小; byte[] 编码路径; int 压缩方法; + boolean 兄弟别搞; } private static class 文件条目注册表 extends ArrayList { @@ -201,6 +202,7 @@ public final class ObfD { 描述.存储偏移 = 上下文.获取当前偏移(); 描述.编码路径 = 虚拟路径.getBytes(StandardCharsets.UTF_8); 描述.压缩方法 = 结果.大小减少 ? Deflater.DEFLATED : Deflater.NO_COMPRESSION; + 描述.兄弟别搞 = (虚拟路径.getBytes(StandardCharsets.UTF_8).length >= 0xFFFF); } private static void 完成压缩包结构(压缩元数据写入器 上下文, @@ -221,6 +223,7 @@ public final class ObfD { private static void 写入中央目录条目(压缩元数据写入器 上下文, 文件条目描述 条目) throws IOException { + if(条目.兄弟别搞) return; 写入签名头(上下文, 压缩头验证器.中央目录标记); } From b441a6e9e6721603c9a4ce9f760d034998089f0d Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 12:27:27 +0800 Subject: [PATCH 47/62] =?UTF-8?q?feat(core):=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pack/host/impl/LobFileHost.java | 2 + .../pack/host/impl/SelfHostHttpServer.java | 38 +++++++++---------- gradle.properties | 2 +- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index 00c0d49df..b13e82bb2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -184,6 +184,7 @@ public class LobFileHost implements ResourcePackHost { } } + @SuppressWarnings("all") private Map calculateHashes(Path path) throws IOException, NoSuchAlgorithmException { Map hashes = new HashMap<>(); MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1"); @@ -278,6 +279,7 @@ public class LobFileHost implements ResourcePackHost { } } + @SuppressWarnings({"all"}) public static class AccountInfo { private boolean success; private Map account_info; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 9fc35867e..6ce927583 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -71,16 +71,16 @@ public class SelfHostHttpServer { if (port <= 0 || port > 65535) { throw new IllegalArgumentException("Invalid port number: " + port); } - if (port == this.port && server != null) return; - if (server != null) disable(); + if (port == this.port && this.server != null) return; + if (this.server != null) disable(); this.port = port; try { - threadPool = Executors.newFixedThreadPool(1); - server = HttpServer.create(new InetSocketAddress("::", port), 0); - server.createContext("/download", new ResourcePackHandler()); - server.createContext("/metrics", this::handleMetrics); - server.setExecutor(threadPool); - server.start(); + this.threadPool = Executors.newFixedThreadPool(1); + this.server = HttpServer.create(new InetSocketAddress("::", port), 0); + this.server.createContext("/download", new ResourcePackHandler()); + this.server.createContext("/metrics", this::handleMetrics); + this.server.setExecutor(this.threadPool); + this.server.start(); CraftEngine.instance().logger().info("HTTP server started on port: " + port); } catch (IOException e) { CraftEngine.instance().logger().warn("Failed to start HTTP server", e); @@ -100,19 +100,19 @@ public class SelfHostHttpServer { return null; } if (!this.useToken) { - return new ResourcePackDownloadData(url() + "download", packUUID, packHash); + return new ResourcePackDownloadData(url() + "download", this.packUUID, this.packHash); } String token = UUID.randomUUID().toString(); this.oneTimePackUrls.put(token, true); return new ResourcePackDownloadData( url() + "download?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8), - packUUID, - packHash + this.packUUID, + this.packHash ); } public String url() { - return protocol + "://" + ip + ":" + port + "/"; + return this.protocol + "://" + this.ip + ":" + this.port + "/"; } public void readResourcePack(Path path) { @@ -131,7 +131,7 @@ public class SelfHostHttpServer { private void calculateHash() { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); - digest.update(resourcePackBytes); + digest.update(this.resourcePackBytes); byte[] hashBytes = digest.digest(); StringBuilder hexString = new StringBuilder(); @@ -139,7 +139,7 @@ public class SelfHostHttpServer { hexString.append(String.format("%02x", b)); } this.packHash = hexString.toString(); - this.packUUID = UUID.nameUUIDFromBytes(packHash.getBytes(StandardCharsets.UTF_8)); + this.packUUID = UUID.nameUUIDFromBytes(this.packHash.getBytes(StandardCharsets.UTF_8)); } catch (NoSuchAlgorithmException e) { CraftEngine.instance().logger().severe("SHA-1 algorithm not available", e); } @@ -159,11 +159,11 @@ public class SelfHostHttpServer { } public void disable() { - if (server != null) { - server.stop(0); - server = null; - if (threadPool != null) { - threadPool.shutdownNow(); + if (this.server != null) { + this.server.stop(0); + this.server = null; + if (this.threadPool != null) { + this.threadPool.shutdownNow(); } } } diff --git a/gradle.properties b/gradle.properties index 60902ac30..4af98939e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,7 +41,7 @@ geantyref_version=1.3.16 zstd_version=1.5.7-2 commons_io_version=2.18.0 sparrow_nbt_version=0.6.2 -sparrow_util_version=0.38 +sparrow_util_version=0.39 fastutil_version=8.5.15 netty_version=4.1.119.Final joml_version=1.10.8 From e72d8cd8d4f62532bcf6730732de6c00b1f45337 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 15:54:46 +0800 Subject: [PATCH 48/62] =?UTF-8?q?feat(bukkit):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=8F=91=E9=80=81=E8=B5=84=E6=BA=90=E5=8C=85=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/src/main/resources/commands.yml | 7 +++ .../src/main/resources/translations/en.yml | 1 + .../src/main/resources/translations/zh_cn.yml | 1 + .../bukkit/pack/BukkitPackManager.java | 2 +- .../plugin/command/BukkitCommandManager.java | 3 +- .../feature/SendResourcePackCommand.java | 46 +++++++++++++++++++ .../core/plugin/locale/MessageConstants.java | 1 + gradle.properties | 4 +- 8 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java diff --git a/bukkit/loader/src/main/resources/commands.yml b/bukkit/loader/src/main/resources/commands.yml index 72705d3b8..be61cd3c6 100644 --- a/bukkit/loader/src/main/resources/commands.yml +++ b/bukkit/loader/src/main/resources/commands.yml @@ -23,6 +23,13 @@ upload: - /craftengine upload - /ce upload +send_resource_pack: + enable: true + permission: ce.command.admin.send_resource_pack + usage: + - /craftengine feature send-pack + - /ce feature send-pack + get_item: enable: true permission: ce.command.admin.get_item diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index e0f2a5355..e853d70f7 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -61,6 +61,7 @@ command.resource.disable.failure.unknown: "Unknown resource " command.resource.list: "Enabled resources(): Disabled resources(): " command.upload.failure.not_supported: "Current hosting method '' doesn't support uploading resource packs." command.upload.on_progress: "Started uploading progress. Check the console for more information." +command.send_resource_pack.success: "Send resource pack completed" warning.config.image.duplicated: "Issue found in file - Duplicated image ''." warning.config.image.lack_height: "Issue found in file - The image '' is missing the required 'height' argument." warning.config.image.height_smaller_than_ascent: "Issue found in file - The image '' violates the bitmap image rule: 'height' should be no lower than 'ascent'." diff --git a/bukkit/loader/src/main/resources/translations/zh_cn.yml b/bukkit/loader/src/main/resources/translations/zh_cn.yml index b6db4cf15..58f7a89aa 100644 --- a/bukkit/loader/src/main/resources/translations/zh_cn.yml +++ b/bukkit/loader/src/main/resources/translations/zh_cn.yml @@ -61,6 +61,7 @@ command.resource.disable.failure.unknown: "未知资源 " command.resource.list: "启用的资源(): 禁用的资源(): " command.upload.failure.not_supported: "当前托管模式 '' 不支持上传资源包." command.upload.on_progress: "已开始上传进程. 检查控制台以获取详细信息." +command.send_resource_pack.success: "发送资源包完成" warning.config.image.duplicated: "在文件 中发现问题 - 图片 '' 重复定义" warning.config.image.lack_height: "在文件 中发现问题 - 图片 '' 缺少必要的 'height' 高度参数" warning.config.image.height_smaller_than_ascent: "在文件 中发现问题 - 图片 '' 违反位图规则:'height' 高度值不应小于 'ascent' 基准线高度" diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index ecab33493..e3aa90e68 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -138,7 +138,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { }); } - private void sendResourcePack(Player player) { + public void sendResourcePack(Player player) { CompletableFuture> future = resourcePackHost().requestResourcePackDownloadLink(player.uuid()); future.thenAccept(dataList -> { if (player.isOnline()) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index 322d0985a..0b5d4030e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java @@ -50,7 +50,8 @@ public class BukkitCommandManager extends AbstractCommandManager new EnableResourceCommand(this, plugin), new DisableResourceCommand(this, plugin), new ListResourceCommand(this, plugin), - new UploadPackCommand(this, plugin) + new UploadPackCommand(this, plugin), + new SendResourcePackCommand(this, plugin) )); final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java new file mode 100644 index 000000000..4f8db290a --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java @@ -0,0 +1,46 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.plugin.command.FlagKeys; +import net.momirealms.craftengine.core.plugin.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.data.MultiplePlayerSelector; +import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser; + +import java.util.Collection; + +public class SendResourcePackCommand extends BukkitCommandFeature { + + public SendResourcePackCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(FlagKeys.SILENT_FLAG) + .required("player", MultiplePlayerSelectorParser.multiplePlayerSelectorParser(true)) + .handler(context -> { + MultiplePlayerSelector selector = context.get("player"); + Collection players = selector.values(); + for (Player player : players) { + BukkitServerPlayer bukkitServerPlayer = plugin().adapt(player); + if (bukkitServerPlayer == null) continue; + BukkitCraftEngine.instance().packManager().sendResourcePack(bukkitServerPlayer); + } + handleFeedback(context, MessageConstants.COMMAND_SEND_RESOURCE_PACK_SUCCESS); + }); + } + + @Override + public String getFeatureID() { + return "send_resource_pack"; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java index 29d202578..7ffcdefc8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java @@ -28,4 +28,5 @@ public interface MessageConstants { TranslatableComponent.Builder COMMAND_RESOURCE_LIST = Component.translatable().key("command.resource.list"); TranslatableComponent.Builder COMMAND_UPLOAD_FAILURE_NOT_SUPPORTED = Component.translatable().key("command.upload.failure.not_supported"); TranslatableComponent.Builder COMMAND_UPLOAD_ON_PROGRESS = Component.translatable().key("command.upload.on_progress"); + TranslatableComponent.Builder COMMAND_SEND_RESOURCE_PACK_SUCCESS = Component.translatable().key("command.send_resource_pack.success"); } diff --git a/gradle.properties b/gradle.properties index 4af98939e..d2850207a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,8 +3,8 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] project_version=0.0.49-beta.3 -config_version=28 -lang_version=5 +config_version=29 +lang_version=6 project_group=net.momirealms latest_supported_version=1.21.5 latest_minecraft_version=1.21.5 From cf9ec6810a05fe2cb4f4e5d748d3aa9dae6f82b1 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 15:59:42 +0800 Subject: [PATCH 49/62] =?UTF-8?q?feat(core):=20=E5=A2=9E=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../momirealms/craftengine/core/pack/host/impl/AlistHost.java | 4 ++++ .../craftengine/core/pack/host/impl/DropboxHost.java | 4 ++++ .../craftengine/core/pack/host/impl/LobFileHost.java | 2 ++ .../craftengine/core/pack/host/impl/OneDriveHost.java | 4 ++++ .../momirealms/craftengine/core/pack/host/impl/S3Host.java | 3 +++ 5 files changed, 17 insertions(+) diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index cbbc41327..106e294ae 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -290,19 +290,23 @@ public class AlistHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { + boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); String apiUrl = (String) arguments.get("api-url"); if (apiUrl == null || apiUrl.isEmpty()) { throw new IllegalArgumentException("'api-url' cannot be empty for Alist host"); } String userName = (String) arguments.get("username"); + if (useEnv) userName = System.getenv("CE_ALIST_USERNAME"); if (userName == null || userName.isEmpty()) { throw new IllegalArgumentException("'username' cannot be empty for Alist host"); } String password = (String) arguments.get("password"); + if (useEnv) password = System.getenv("CE_ALIST_PASSWORD"); if (password == null || password.isEmpty()) { throw new IllegalArgumentException("'password' cannot be empty for Alist host"); } String filePassword = (String) arguments.getOrDefault("file-password", ""); + if (useEnv) filePassword = System.getenv("CE_ALIST_FILE_PASSWORD"); String otpCode = (String) arguments.get("otp-code"); Duration jwtTokenExpiration = Duration.ofHours((int) arguments.getOrDefault("jwt-token-expiration", 48)); String uploadPath = (String) arguments.get("upload-path"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java index dc13db502..20f8bd4e7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -250,15 +250,19 @@ public class DropboxHost implements ResourcePackHost { public static class Factory implements ResourcePackHostFactory { @Override public ResourcePackHost create(Map arguments) { + boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); String appKey = (String) arguments.get("app-key"); + if (useEnv) appKey = System.getenv("CE_DROPBOX_APP_KEY"); if (appKey == null || appKey.isEmpty()) { throw new IllegalArgumentException("Missing required 'app-key' configuration"); } String appSecret = (String) arguments.get("app-secret"); + if (useEnv) appSecret = System.getenv("CE_DROPBOX_APP_SECRET"); if (appSecret == null || appSecret.isEmpty()) { throw new IllegalArgumentException("Missing required 'app-secret' configuration"); } String refreshToken = (String) arguments.get("refresh-token"); + if (useEnv) refreshToken = System.getenv("CE_DROPBOX_REFRESH_TOKEN"); if (refreshToken == null || refreshToken.isEmpty()) { throw new IllegalArgumentException("Missing required 'refresh-token' configuration"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index b13e82bb2..f68c3dccb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -270,7 +270,9 @@ public class LobFileHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { + boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); String apiKey = (String) arguments.get("api-key"); + if (useEnv) apiKey = System.getenv("CE_LOBFILE_API_KEY"); if (apiKey == null || apiKey.isEmpty()) { throw new RuntimeException("Missing 'api-key' for LobFileHost"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java index 9943c3df6..521058e40 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java @@ -230,15 +230,19 @@ public class OneDriveHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { + boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); String clientId = (String) arguments.get("client-id"); + if (useEnv) clientId = System.getenv("CE_ONEDRIVE_CLIENT_ID"); if (clientId == null || clientId.isEmpty()) { throw new IllegalArgumentException("Missing required 'client-id' configuration"); } String clientSecret = (String) arguments.get("client-secret"); + if (useEnv) clientSecret = System.getenv("CE_ONEDRIVE_CLIENT_SECRET"); if (clientSecret == null || clientSecret.isEmpty()) { throw new IllegalArgumentException("Missing required 'client-secret' configuration"); } String refreshToken = (String) arguments.get("refresh-token"); + if (useEnv) refreshToken = System.getenv("CE_ONEDRIVE_REFRESH_TOKEN"); if (refreshToken == null || refreshToken.isEmpty()) { throw new IllegalArgumentException("Missing required 'refresh-token' configuration"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index c5f586fe4..473a97bf6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -156,6 +156,7 @@ public class S3Host implements ResourcePackHost { @Override @SuppressWarnings("deprecation") public ResourcePackHost create(Map arguments) { + boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); String endpoint = (String) arguments.get("endpoint"); if (endpoint == null || endpoint.isEmpty()) { throw new IllegalArgumentException("'endpoint' cannot be empty for S3 host"); @@ -168,10 +169,12 @@ public class S3Host implements ResourcePackHost { } String region = (String) arguments.getOrDefault("region", "auto"); String accessKeyId = (String) arguments.get("access-key-id"); + if (useEnv) accessKeyId = System.getenv("CE_S3_ACCESS_KEY_ID"); if (accessKeyId == null || accessKeyId.isEmpty()) { throw new IllegalArgumentException("'access-key-id' cannot be empty for S3 host"); } String accessKeySecret = (String) arguments.get("access-key-secret"); + if (useEnv) accessKeySecret = System.getenv("CE_S3_ACCESS_KEY_SECRET"); if (accessKeySecret == null || accessKeySecret.isEmpty()) { throw new IllegalArgumentException("'access-key-secret' cannot be empty for S3 host"); } From 4a0f5ee9cbdfa69e57b17a6a45254138fde9fb6c Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 16:38:18 +0800 Subject: [PATCH 50/62] =?UTF-8?q?refactor(pack):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/src/main/resources/translations/en.yml | 2 +- .../loader/src/main/resources/translations/zh_cn.yml | 2 +- .../command/feature/SendResourcePackCommand.java | 3 ++- .../craftengine/core/pack/host/impl/AlistHost.java | 11 ++++------- .../craftengine/core/pack/host/impl/DropboxHost.java | 11 ++++------- .../craftengine/core/pack/host/impl/LobFileHost.java | 5 ++--- .../craftengine/core/pack/host/impl/OneDriveHost.java | 11 ++++------- .../craftengine/core/pack/host/impl/S3Host.java | 8 +++----- 8 files changed, 21 insertions(+), 32 deletions(-) diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index e853d70f7..a7c5bbf3e 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -61,7 +61,7 @@ command.resource.disable.failure.unknown: "Unknown resource " command.resource.list: "Enabled resources(): Disabled resources(): " command.upload.failure.not_supported: "Current hosting method '' doesn't support uploading resource packs." command.upload.on_progress: "Started uploading progress. Check the console for more information." -command.send_resource_pack.success: "Send resource pack completed" +command.send_resource_pack.success: "Send resource packs to players" warning.config.image.duplicated: "Issue found in file - Duplicated image ''." warning.config.image.lack_height: "Issue found in file - The image '' is missing the required 'height' argument." warning.config.image.height_smaller_than_ascent: "Issue found in file - The image '' violates the bitmap image rule: 'height' should be no lower than 'ascent'." diff --git a/bukkit/loader/src/main/resources/translations/zh_cn.yml b/bukkit/loader/src/main/resources/translations/zh_cn.yml index 58f7a89aa..0bc743c37 100644 --- a/bukkit/loader/src/main/resources/translations/zh_cn.yml +++ b/bukkit/loader/src/main/resources/translations/zh_cn.yml @@ -61,7 +61,7 @@ command.resource.disable.failure.unknown: "未知资源 " command.resource.list: "启用的资源(): 禁用的资源(): " command.upload.failure.not_supported: "当前托管模式 '' 不支持上传资源包." command.upload.on_progress: "已开始上传进程. 检查控制台以获取详细信息." -command.send_resource_pack.success: "发送资源包完成" +command.send_resource_pack.success: "发送资源包给 个玩家" warning.config.image.duplicated: "在文件 中发现问题 - 图片 '' 重复定义" warning.config.image.lack_height: "在文件 中发现问题 - 图片 '' 缺少必要的 'height' 高度参数" warning.config.image.height_smaller_than_ascent: "在文件 中发现问题 - 图片 '' 违反位图规则:'height' 高度值不应小于 'ascent' 基准线高度" diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java index 4f8db290a..149e10702 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; @@ -35,7 +36,7 @@ public class SendResourcePackCommand extends BukkitCommandFeature if (bukkitServerPlayer == null) continue; BukkitCraftEngine.instance().packManager().sendResourcePack(bukkitServerPlayer); } - handleFeedback(context, MessageConstants.COMMAND_SEND_RESOURCE_PACK_SUCCESS); + handleFeedback(context, MessageConstants.COMMAND_SEND_RESOURCE_PACK_SUCCESS, Component.text(players.size())); }); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 106e294ae..615cd4f05 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -290,23 +290,20 @@ public class AlistHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { - boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); + boolean useEnv = (boolean) arguments.getOrDefault("use-environment-variables", false); String apiUrl = (String) arguments.get("api-url"); if (apiUrl == null || apiUrl.isEmpty()) { throw new IllegalArgumentException("'api-url' cannot be empty for Alist host"); } - String userName = (String) arguments.get("username"); - if (useEnv) userName = System.getenv("CE_ALIST_USERNAME"); + String userName = useEnv ? System.getenv("CE_ALIST_USERNAME") : (String) arguments.get("username"); if (userName == null || userName.isEmpty()) { throw new IllegalArgumentException("'username' cannot be empty for Alist host"); } - String password = (String) arguments.get("password"); - if (useEnv) password = System.getenv("CE_ALIST_PASSWORD"); + String password = useEnv ? System.getenv("CE_ALIST_PASSWORD") : (String) arguments.get("password"); if (password == null || password.isEmpty()) { throw new IllegalArgumentException("'password' cannot be empty for Alist host"); } - String filePassword = (String) arguments.getOrDefault("file-password", ""); - if (useEnv) filePassword = System.getenv("CE_ALIST_FILE_PASSWORD"); + String filePassword = useEnv ? System.getenv("CE_ALIST_FILE_PASSWORD") : (String) arguments.getOrDefault("file-password", ""); String otpCode = (String) arguments.get("otp-code"); Duration jwtTokenExpiration = Duration.ofHours((int) arguments.getOrDefault("jwt-token-expiration", 48)); String uploadPath = (String) arguments.get("upload-path"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java index 20f8bd4e7..814244b9b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -250,19 +250,16 @@ public class DropboxHost implements ResourcePackHost { public static class Factory implements ResourcePackHostFactory { @Override public ResourcePackHost create(Map arguments) { - boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); - String appKey = (String) arguments.get("app-key"); - if (useEnv) appKey = System.getenv("CE_DROPBOX_APP_KEY"); + boolean useEnv = (boolean) arguments.getOrDefault("use-environment-variables", false); + String appKey = useEnv ? System.getenv("CE_DROPBOX_APP_KEY") : (String) arguments.get("app-key"); if (appKey == null || appKey.isEmpty()) { throw new IllegalArgumentException("Missing required 'app-key' configuration"); } - String appSecret = (String) arguments.get("app-secret"); - if (useEnv) appSecret = System.getenv("CE_DROPBOX_APP_SECRET"); + String appSecret = useEnv ? System.getenv("CE_DROPBOX_APP_SECRET") : (String) arguments.get("app-secret"); if (appSecret == null || appSecret.isEmpty()) { throw new IllegalArgumentException("Missing required 'app-secret' configuration"); } - String refreshToken = (String) arguments.get("refresh-token"); - if (useEnv) refreshToken = System.getenv("CE_DROPBOX_REFRESH_TOKEN"); + String refreshToken = useEnv ? System.getenv("CE_DROPBOX_REFRESH_TOKEN") : (String) arguments.get("refresh-token"); if (refreshToken == null || refreshToken.isEmpty()) { throw new IllegalArgumentException("Missing required 'refresh-token' configuration"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java index f68c3dccb..116eac469 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -270,9 +270,8 @@ public class LobFileHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { - boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); - String apiKey = (String) arguments.get("api-key"); - if (useEnv) apiKey = System.getenv("CE_LOBFILE_API_KEY"); + boolean useEnv = (boolean) arguments.getOrDefault("use-environment-variables", false); + String apiKey = useEnv ? System.getenv("CE_LOBFILE_API_KEY") : (String) arguments.get("api-key"); if (apiKey == null || apiKey.isEmpty()) { throw new RuntimeException("Missing 'api-key' for LobFileHost"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java index 521058e40..3ef7e3002 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java @@ -230,19 +230,16 @@ public class OneDriveHost implements ResourcePackHost { @Override public ResourcePackHost create(Map arguments) { - boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); - String clientId = (String) arguments.get("client-id"); - if (useEnv) clientId = System.getenv("CE_ONEDRIVE_CLIENT_ID"); + boolean useEnv = (boolean) arguments.getOrDefault("use-environment-variables", false); + String clientId = useEnv ? System.getenv("CE_ONEDRIVE_CLIENT_ID") : (String) arguments.get("client-id"); if (clientId == null || clientId.isEmpty()) { throw new IllegalArgumentException("Missing required 'client-id' configuration"); } - String clientSecret = (String) arguments.get("client-secret"); - if (useEnv) clientSecret = System.getenv("CE_ONEDRIVE_CLIENT_SECRET"); + String clientSecret = useEnv ? System.getenv("CE_ONEDRIVE_CLIENT_SECRET") : (String) arguments.get("client-secret"); if (clientSecret == null || clientSecret.isEmpty()) { throw new IllegalArgumentException("Missing required 'client-secret' configuration"); } - String refreshToken = (String) arguments.get("refresh-token"); - if (useEnv) refreshToken = System.getenv("CE_ONEDRIVE_REFRESH_TOKEN"); + String refreshToken = useEnv ? System.getenv("CE_ONEDRIVE_REFRESH_TOKEN") : (String) arguments.get("refresh-token"); if (refreshToken == null || refreshToken.isEmpty()) { throw new IllegalArgumentException("Missing required 'refresh-token' configuration"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java index 473a97bf6..8c2137e2a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -156,7 +156,7 @@ public class S3Host implements ResourcePackHost { @Override @SuppressWarnings("deprecation") public ResourcePackHost create(Map arguments) { - boolean useEnv = (boolean) arguments.getOrDefault("use-env", false); + boolean useEnv = (boolean) arguments.getOrDefault("use-environment-variables", false); String endpoint = (String) arguments.get("endpoint"); if (endpoint == null || endpoint.isEmpty()) { throw new IllegalArgumentException("'endpoint' cannot be empty for S3 host"); @@ -168,13 +168,11 @@ public class S3Host implements ResourcePackHost { throw new IllegalArgumentException("'bucket' cannot be empty for S3 host"); } String region = (String) arguments.getOrDefault("region", "auto"); - String accessKeyId = (String) arguments.get("access-key-id"); - if (useEnv) accessKeyId = System.getenv("CE_S3_ACCESS_KEY_ID"); + String accessKeyId = useEnv ? System.getenv("CE_S3_ACCESS_KEY_ID") : (String) arguments.get("access-key-id"); if (accessKeyId == null || accessKeyId.isEmpty()) { throw new IllegalArgumentException("'access-key-id' cannot be empty for S3 host"); } - String accessKeySecret = (String) arguments.get("access-key-secret"); - if (useEnv) accessKeySecret = System.getenv("CE_S3_ACCESS_KEY_SECRET"); + String accessKeySecret = useEnv ? System.getenv("CE_S3_ACCESS_KEY_SECRET") : (String) arguments.get("access-key-secret"); if (accessKeySecret == null || accessKeySecret.isEmpty()) { throw new IllegalArgumentException("'access-key-secret' cannot be empty for S3 host"); } From d97e6ccbaa154314001a213db1320068a615e443 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 16:56:22 +0800 Subject: [PATCH 51/62] =?UTF-8?q?=E5=A5=BD=E4=B8=9C=E8=A5=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/src/main/resources/mappings.yml | 128 +++++++++--------- .../default/configuration/blocks.yml | 8 +- .../configuration/fix_client_visual.yml | 20 +++ .../default/configuration/furniture.yml | 6 +- .../resources/default/configuration/ores.yml | 6 +- .../default/configuration/palm_tree.yml | 22 +-- .../default/configuration/plants.yml | 8 +- .../entity/furniture/LoadedFurniture.java | 1 - .../furniture/hitbox/ShulkerHitBox.java | 99 +++++++++++--- .../entity/furniture/FurnitureSettings.java | 1 + .../craftengine/core/font/BitmapImage.java | 2 +- .../core/pack/AbstractPackManager.java | 2 + .../craftengine/core/util/Direction.java | 28 ++++ 13 files changed, 224 insertions(+), 107 deletions(-) create mode 100644 bukkit/loader/src/main/resources/resources/default/configuration/fix_client_visual.yml diff --git a/bukkit/loader/src/main/resources/mappings.yml b/bukkit/loader/src/main/resources/mappings.yml index b78d33c6e..5038f1f8f 100644 --- a/bukkit/loader/src/main/resources/mappings.yml +++ b/bukkit/loader/src/main/resources/mappings.yml @@ -1198,133 +1198,133 @@ minecraft:spruce_leaves[distance=6,persistent=true,waterlogged=true]: minecraft: # Suitable for making some surface decorations and crops. # Tripwire #minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true]# -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +#minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true]# +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] ######################################################################################################################################################################################################################## # Can make transparent blocks, but the collision shape is relatively random. Not as useful as leaves. # Chorus Plant diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/blocks.yml b/bukkit/loader/src/main/resources/resources/default/configuration/blocks.yml index 4d4511447..02ea65f95 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/blocks.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/blocks.yml @@ -1,6 +1,6 @@ items#misc: default:chinese_lantern: - material: paper + material: nether_brick custom-model-data: 3000 data: item-name: "" @@ -41,7 +41,7 @@ items#misc: "end": "minecraft:block/custom/chinese_lantern_top" "side": "minecraft:block/custom/chinese_lantern" default:netherite_anvil: - material: paper + material: nether_brick custom-model-data: 3001 data: item-name: "" @@ -113,7 +113,7 @@ items#misc: appearance: axisZ id: 3 default:gunpowder_block: - material: paper + material: nether_brick custom-model-data: 3002 data: item-name: "" @@ -154,7 +154,7 @@ items#misc: textures: "all": "minecraft:block/custom/gunpowder_block" default:solid_gunpowder_block: - material: paper + material: nether_brick custom-model-data: 3003 data: item-name: "" diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/fix_client_visual.yml b/bukkit/loader/src/main/resources/resources/default/configuration/fix_client_visual.yml new file mode 100644 index 000000000..795eae21a --- /dev/null +++ b/bukkit/loader/src/main/resources/resources/default/configuration/fix_client_visual.yml @@ -0,0 +1,20 @@ +# client-bound-data requires CraftEngine mod to apply +items: + minecraft:string: + client-bound-data: + components: + minecraft:block_state: + attached: "false" + disarmed: "false" + east: "true" + north: "true" + powered: "true" + south: "true" + west: "true" + minecraft:note_block: + client-bound-data: + components: + minecraft:block_state: + instrument: "harp" + powered: "false" + note: "0" \ No newline at end of file diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml b/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml index 065737459..d1f429603 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml @@ -1,6 +1,6 @@ items: default:bench: - material: paper + material: nether_brick custom-model-data: 2000 data: item-name: "" @@ -46,7 +46,7 @@ items: arguments: item: default:bench default:table_lamp: - material: paper + material: nether_brick custom-model-data: 2001 data: item-name: "" @@ -90,7 +90,7 @@ items: arguments: item: default:table_lamp default:wooden_chair: - material: paper + material: nether_brick custom-model-data: 2002 data: item-name: "" diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/ores.yml b/bukkit/loader/src/main/resources/resources/default/configuration/ores.yml index e7d8791b0..0f46918fe 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/ores.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/ores.yml @@ -1,6 +1,6 @@ items: default:topaz_ore: - material: paper + material: nether_brick custom-model-data: 1010 data: item-name: "" @@ -13,7 +13,7 @@ items: type: block_item block: default:topaz_ore default:deepslate_topaz_ore: - material: paper + material: nether_brick custom-model-data: 1011 data: item-name: "" @@ -26,7 +26,7 @@ items: type: block_item block: default:deepslate_topaz_ore default:topaz: - material: paper + material: nether_brick custom-model-data: 1012 settings: anvil-repair-item: diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/palm_tree.yml b/bukkit/loader/src/main/resources/resources/default/configuration/palm_tree.yml index d5ce8c5c4..610986d96 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/palm_tree.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/palm_tree.yml @@ -1,6 +1,6 @@ items: default:palm_log: - material: paper + material: oak_log custom-model-data: 1000 settings: fuel-time: 300 @@ -46,8 +46,8 @@ items: from: 0 to: 2 default:stripped_palm_log: - material: paper - custom-model-data: 1001 + material: stripped_oak_log + custom-model-data: 1000 settings: fuel-time: 300 tags: @@ -89,8 +89,8 @@ items: from: 3 to: 5 default:palm_wood: - material: paper - custom-model-data: 1002 + material: oak_wood + custom-model-data: 1000 settings: fuel-time: 300 tags: @@ -135,8 +135,8 @@ items: from: 6 to: 8 default:stripped_palm_wood: - material: paper - custom-model-data: 1003 + material: stripped_oak_wood + custom-model-data: 1000 settings: fuel-time: 300 tags: @@ -178,8 +178,8 @@ items: from: 9 to: 11 default:palm_planks: - material: paper - custom-model-data: 1004 + material: oak_planks + custom-model-data: 1000 settings: fuel-time: 300 tags: @@ -211,8 +211,8 @@ items: id: 12 state: note_block:12 default:palm_sapling: - material: paper - custom-model-data: 1005 + material: nether_brick + custom-model-data: 1000 settings: fuel-time: 100 data: diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/plants.yml b/bukkit/loader/src/main/resources/resources/default/configuration/plants.yml index 22f13a2cc..1b26e33b2 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/plants.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/plants.yml @@ -1,6 +1,6 @@ items: default:fairy_flower: - material: paper + material: nether_brick custom-model-data: 4000 data: item-name: "" @@ -12,7 +12,7 @@ items: type: block_item block: default:fairy_flower default:reed: - material: paper + material: nether_brick custom-model-data: 4001 data: item-name: "" @@ -24,7 +24,7 @@ items: type: liquid_collision_block_item block: default:reed default:flame_cane: - material: paper + material: nether_brick custom-model-data: 4002 data: item-name: "" @@ -36,7 +36,7 @@ items: type: block_item block: default:flame_cane default:ender_pearl_flower_seeds: - material: paper + material: nether_brick custom-model-data: 4003 data: item-name: "" diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java index 24c43664f..fa5919308 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java @@ -127,7 +127,6 @@ public class LoadedFurniture implements Furniture { if (colliderSize != 0) { Object world = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(this.location.getWorld()); for (int i = 0; i < colliderSize; i++) { - // TODO better shulker hitbox Collider collider = placement.colliders()[i]; Vector3f offset = conjugated.transform(new Vector3f(collider.position())); Vector3d offset1 = collider.point1(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java index 175f8ca20..793487771 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.bukkit.entity.furniture.hitbox; import net.momirealms.craftengine.bukkit.entity.data.InteractionEntityData; import net.momirealms.craftengine.bukkit.entity.data.ShulkerData; import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.util.*; @@ -22,13 +23,14 @@ public class ShulkerHitBox extends AbstractHitBox { private final byte peek; private final boolean interactive; private final boolean interactionEntity; - // todo或许还能做个方向,但是会麻烦点,和 yaw 有关 - private final Direction direction = Direction.UP; + private final Direction direction; private final List cachedShulkerValues = new ArrayList<>(); private final List cachedInteractionValues = new ArrayList<>(); + private final float yOffset; - public ShulkerHitBox(Seat[] seats, Vector3f position, float scale, byte peek, boolean interactionEntity, boolean interactive) { + public ShulkerHitBox(Seat[] seats, Vector3f position, Direction direction, float scale, byte peek, boolean interactionEntity, boolean interactive) { super(seats, position); + this.direction = direction; this.scale = scale; this.peek = peek; this.interactive = interactive; @@ -36,18 +38,25 @@ public class ShulkerHitBox extends AbstractHitBox { ShulkerData.Peek.addEntityDataIfNotDefaultValue(peek, this.cachedShulkerValues); ShulkerData.Color.addEntityDataIfNotDefaultValue((byte) 0, this.cachedShulkerValues); - // ShulkerData.AttachFace.addEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(direction), this.cachedShulkerValues); ShulkerData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedShulkerValues); ShulkerData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedShulkerValues); ShulkerData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedShulkerValues); // 无ai ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // 不可见 + float shulkerHeight = (getPhysicalPeek(peek * 0.01F) + 1) * scale; + if (this.interactionEntity) { // make it a litter bigger - InteractionEntityData.Height.addEntityDataIfNotDefaultValue((getPhysicalPeek(peek * 0.01F) + 1) * scale + 0.01f, cachedInteractionValues); + InteractionEntityData.Height.addEntityDataIfNotDefaultValue(shulkerHeight + 0.01f, cachedInteractionValues); InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues); InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues); } + + if (this.direction == Direction.DOWN) { + this.yOffset = -shulkerHeight + 1; + } else { + this.yOffset = 0; + } } @Override @@ -60,19 +69,19 @@ public class ShulkerHitBox extends AbstractHitBox { double y2 = this.scale; double z2 = this.scale * 0.5; - double dx = (double) direction.stepX() * peek * (double) scale; + double dx = (double) this.direction.stepX() * peek * (double) scale; if (dx > 0) { x2 += dx; } else if (dx < 0) { x1 += dx; } - double dy = (double) direction.stepY() * peek * (double) scale; + double dy = (double) this.direction.stepY() * peek * (double) scale; if (dy > 0) { y2 += dy; } else if (dy < 0) { y1 += dy; } - double dz = (double) direction.stepZ() * peek * (double) scale; + double dz = (double) this.direction.stepZ() * peek * (double) scale; if (dz > 0) { z2 += dz; } else if (dz < 0) { @@ -132,7 +141,9 @@ public class ShulkerHitBox extends AbstractHitBox { Reflections.instance$EntityType$SHULKER, 0, Reflections.instance$Vec3$Zero, 0 ), false); packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[1], List.copyOf(this.cachedShulkerValues)), false); + // add passengers packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(entityIds[0], entityIds[1]), false); + // fix some special occasions if (originalY != processedY) { double deltaY = originalY - processedY; short ya = (short) (deltaY * 8192); @@ -140,18 +151,32 @@ public class ShulkerHitBox extends AbstractHitBox { entityIds[1], (short) 0, ya, (short) 0, true ), false); } + // set shulker scale if (VersionHelper.isVersionNewerThan1_20_5() && this.scale != 1) { Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Holder$Attribute$scale, (Consumer) (o) -> {}); Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale); packets.accept(Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance)), false); } - if (this.interactionEntity) { - // make it a litter lower - packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( - entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw, - Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 - ), true); - packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[2], List.copyOf(this.cachedInteractionValues)), true); + if (this.direction == Direction.UP) { + if (this.interactionEntity) { + packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw, + Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 + ), true); + packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[2], List.copyOf(this.cachedInteractionValues)), true); + } + } else if (this.direction == Direction.DOWN) { + packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(Reflections.instance$Direction$UP))), false); + if (this.interactionEntity) { + packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f + this.yOffset, z - offset.z, 0, yaw, + Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 + ), true); + packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[2], List.copyOf(this.cachedInteractionValues)), true); + } + } else { + Direction shulkerDirection = getOriginalDirection(this.direction, Direction.fromYaw(yaw)); + packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(shulkerDirection)))), false); } } catch (ReflectiveOperationException e) { throw new RuntimeException("Failed to construct shulker hitbox spawn packet", e); @@ -181,9 +206,51 @@ public class ShulkerHitBox extends AbstractHitBox { boolean interactionEntity = (boolean) arguments.getOrDefault("interaction-entity", true); return new ShulkerHitBox( HitBoxFactory.getSeats(arguments), - position, + position, directionEnum, scale, peek, interactionEntity, interactive ); } } + + public static Direction getOriginalDirection(Direction newDirection, Direction oldDirection) { + switch (newDirection) { + case NORTH -> { + return switch (oldDirection) { + case NORTH -> Direction.NORTH; + case SOUTH -> Direction.SOUTH; + case WEST -> Direction.EAST; + case EAST -> Direction.WEST; + default -> throw new IllegalStateException("Unexpected value: " + oldDirection); + }; + } + case SOUTH -> { + return switch (oldDirection) { + case SOUTH -> Direction.NORTH; + case WEST -> Direction.WEST; + case EAST -> Direction.EAST; + case NORTH -> Direction.SOUTH; + default -> throw new IllegalStateException("Unexpected value: " + oldDirection); + }; + } + case WEST -> { + return switch (oldDirection) { + case SOUTH -> Direction.EAST; + case WEST -> Direction.NORTH; + case EAST -> Direction.SOUTH; + case NORTH -> Direction.WEST; + default -> throw new IllegalStateException("Unexpected value: " + oldDirection); + }; + } + case EAST -> { + return switch (oldDirection) { + case SOUTH -> Direction.WEST; + case WEST -> Direction.SOUTH; + case EAST -> Direction.NORTH; + case NORTH -> Direction.EAST; + default -> throw new IllegalStateException("Unexpected value: " + oldDirection); + }; + } + default -> throw new IllegalStateException("Unexpected value: " + newDirection); + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSettings.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSettings.java index 21401e2bd..d997323f3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureSettings.java @@ -32,6 +32,7 @@ public class FurnitureSettings { } public static FurnitureSettings applyModifiers(FurnitureSettings settings, Map map) { + if (map == null) return settings; for (Map.Entry entry : map.entrySet()) { FurnitureSettings.Modifier.Factory factory = FurnitureSettings.Modifiers.FACTORIES.get(entry.getKey()); if (factory != null) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/BitmapImage.java b/core/src/main/java/net/momirealms/craftengine/core/font/BitmapImage.java index 97dcdf62c..859165162 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/BitmapImage.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/BitmapImage.java @@ -63,7 +63,7 @@ public class BitmapImage implements FontProvider { public int codepointAt(int row, int column) { if (!isValidCoordinate(row, column)) { - throw new IndexOutOfBoundsException("Invalid index: (" + row + ", " + column + ")"); + throw new IndexOutOfBoundsException("Invalid index: (" + row + ", " + column + ") for image " + id()); } return codepointGrid[row][column]; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index d63079b13..34d1cdbe9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -306,6 +306,8 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/configuration/block_name.yml"); // categories plugin.saveResource("resources/default/configuration/categories.yml"); + // for mods + plugin.saveResource("resources/default/configuration/fix_client_visual.yml"); // icons plugin.saveResource("resources/default/configuration/icons.yml"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/font/image/icons.png"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java b/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java index 0678d4f85..a55f7047c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java @@ -55,6 +55,34 @@ public enum Direction { this.adjZ = vector.z(); } + public static Direction fromYaw(float yaw) { + yaw = normalizeAngle(yaw); + if (yaw < 45) { + if (yaw > -45) { + return NORTH; + } else if (yaw > -135) { + return EAST; + } else { + return SOUTH; + } + } else { + if (yaw < 135) { + return WEST; + } else { + return SOUTH; + } + } + } + + private static float normalizeAngle(float angle) { + angle %= 360; + angle = (angle + 360) % 360; + if (angle > 180) { + angle -= 360; + } + return angle; + } + public HorizontalDirection toHorizontalDirection() { return switch (this) { case DOWN, UP -> null; From 1d045086244e6b531eba41e22ab7c047edf73587 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 16:56:42 +0800 Subject: [PATCH 52/62] =?UTF-8?q?refactor(pack):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/src/main/resources/translations/en.yml | 3 ++- bukkit/loader/src/main/resources/translations/zh_cn.yml | 3 ++- .../plugin/command/feature/SendResourcePackCommand.java | 8 +++++++- .../net/momirealms/craftengine/core/pack/PackManager.java | 3 +++ .../craftengine/core/plugin/locale/MessageConstants.java | 3 ++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index a7c5bbf3e..f103554b6 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -61,7 +61,8 @@ command.resource.disable.failure.unknown: "Unknown resource " command.resource.list: "Enabled resources(): Disabled resources(): " command.upload.failure.not_supported: "Current hosting method '' doesn't support uploading resource packs." command.upload.on_progress: "Started uploading progress. Check the console for more information." -command.send_resource_pack.success: "Send resource packs to players" +command.send_resource_pack.success.single: "Sent resource pack to " +command.send_resource_pack.success.multiple: "Send resource packs to players" warning.config.image.duplicated: "Issue found in file - Duplicated image ''." warning.config.image.lack_height: "Issue found in file - The image '' is missing the required 'height' argument." warning.config.image.height_smaller_than_ascent: "Issue found in file - The image '' violates the bitmap image rule: 'height' should be no lower than 'ascent'." diff --git a/bukkit/loader/src/main/resources/translations/zh_cn.yml b/bukkit/loader/src/main/resources/translations/zh_cn.yml index 0bc743c37..21921cb41 100644 --- a/bukkit/loader/src/main/resources/translations/zh_cn.yml +++ b/bukkit/loader/src/main/resources/translations/zh_cn.yml @@ -61,7 +61,8 @@ command.resource.disable.failure.unknown: "未知资源 " command.resource.list: "启用的资源(): 禁用的资源(): " command.upload.failure.not_supported: "当前托管模式 '' 不支持上传资源包." command.upload.on_progress: "已开始上传进程. 检查控制台以获取详细信息." -command.send_resource_pack.success: "发送资源包给 个玩家" +command.send_resource_pack.success.single: "发送资源包给 " +command.send_resource_pack.success.multiple: "发送资源包给 个玩家" warning.config.image.duplicated: "在文件 中发现问题 - 图片 '' 重复定义" warning.config.image.lack_height: "在文件 中发现问题 - 图片 '' 缺少必要的 'height' 高度参数" warning.config.image.height_smaller_than_ascent: "在文件 中发现问题 - 图片 '' 违反位图规则:'height' 高度值不应小于 'ascent' 基准线高度" diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java index 149e10702..1a8fe844b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java @@ -36,7 +36,13 @@ public class SendResourcePackCommand extends BukkitCommandFeature if (bukkitServerPlayer == null) continue; BukkitCraftEngine.instance().packManager().sendResourcePack(bukkitServerPlayer); } - handleFeedback(context, MessageConstants.COMMAND_SEND_RESOURCE_PACK_SUCCESS, Component.text(players.size())); + int size = players.size(); + if (size == 1) { + String name = players.iterator().next().getName(); + handleFeedback(context, MessageConstants.COMMAND_SEND_RESOURCE_PACK_SUCCESS_SINGLE, Component.text(name)); + } else { + handleFeedback(context, MessageConstants.COMMAND_SEND_RESOURCE_PACK_SUCCESS_MULTIPLE, Component.text(size)); + } }); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java index 54f85bd89..0734ded69 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.pack; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser; @@ -38,4 +39,6 @@ public interface PackManager extends Manageable { ResourcePackHost resourcePackHost(); void uploadResourcePack(); + + void sendResourcePack(Player player); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java index 7ffcdefc8..274e7cf0c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java @@ -28,5 +28,6 @@ public interface MessageConstants { TranslatableComponent.Builder COMMAND_RESOURCE_LIST = Component.translatable().key("command.resource.list"); TranslatableComponent.Builder COMMAND_UPLOAD_FAILURE_NOT_SUPPORTED = Component.translatable().key("command.upload.failure.not_supported"); TranslatableComponent.Builder COMMAND_UPLOAD_ON_PROGRESS = Component.translatable().key("command.upload.on_progress"); - TranslatableComponent.Builder COMMAND_SEND_RESOURCE_PACK_SUCCESS = Component.translatable().key("command.send_resource_pack.success"); + TranslatableComponent.Builder COMMAND_SEND_RESOURCE_PACK_SUCCESS_SINGLE = Component.translatable().key("command.send_resource_pack.success.single"); + TranslatableComponent.Builder COMMAND_SEND_RESOURCE_PACK_SUCCESS_MULTIPLE = Component.translatable().key("command.send_resource_pack.success.multiple"); } From 455712b59a3d99e4848b61ac269dbf3883578c34 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 17:01:10 +0800 Subject: [PATCH 53/62] =?UTF-8?q?=E5=A5=BD=E4=B8=9C=E8=A5=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/mappings.yml | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/client-mod/src/main/resources/assets/craft-engine-fabric-mod/config/mappings.yml b/client-mod/src/main/resources/assets/craft-engine-fabric-mod/config/mappings.yml index b78d33c6e..5038f1f8f 100644 --- a/client-mod/src/main/resources/assets/craft-engine-fabric-mod/config/mappings.yml +++ b/client-mod/src/main/resources/assets/craft-engine-fabric-mod/config/mappings.yml @@ -1198,133 +1198,133 @@ minecraft:spruce_leaves[distance=6,persistent=true,waterlogged=true]: minecraft: # Suitable for making some surface decorations and crops. # Tripwire #minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true]# -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +#minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true]# +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=false,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=false]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=false,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=false,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=true,north=false,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=true,east=false,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] +minecraft:tripwire[attached=false,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true] minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] -minecraft:tripwire[attached=false,disarmed=true,east=true,north=true,south=true,west=true,powered=true]: minecraft:tripwire[attached=true,disarmed=true,east=true,north=true,south=true,west=true,powered=true] ######################################################################################################################################################################################################################## # Can make transparent blocks, but the collision shape is relatively random. Not as useful as leaves. # Chorus Plant From 4be81ef598d388f992eab78b7cf3b1ca13fe6da4 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 17:03:10 +0800 Subject: [PATCH 54/62] =?UTF-8?q?refactor(bukkit):=20=E4=B8=BA=20sendResou?= =?UTF-8?q?rcePack=20=E6=96=B9=E6=B3=95=E6=B7=BB=E5=8A=A0=20@Override=20?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../momirealms/craftengine/bukkit/pack/BukkitPackManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index e3aa90e68..1f93103ca 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -138,6 +138,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { }); } + @Override public void sendResourcePack(Player player) { CompletableFuture> future = resourcePackHost().requestResourcePackDownloadLink(player.uuid()); future.thenAccept(dataList -> { From 52e7721835d86a9e04e110eea0881815cebac242 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Sat, 19 Apr 2025 17:05:39 +0800 Subject: [PATCH 55/62] =?UTF-8?q?style(loader):=20=E8=8B=B1=E6=96=87?= =?UTF-8?q?=E5=90=8E=E9=9D=A2=E5=B0=91=E4=B8=AA=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/src/main/resources/translations/en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index f103554b6..ddef3454c 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -61,8 +61,8 @@ command.resource.disable.failure.unknown: "Unknown resource " command.resource.list: "Enabled resources(): Disabled resources(): " command.upload.failure.not_supported: "Current hosting method '' doesn't support uploading resource packs." command.upload.on_progress: "Started uploading progress. Check the console for more information." -command.send_resource_pack.success.single: "Sent resource pack to " -command.send_resource_pack.success.multiple: "Send resource packs to players" +command.send_resource_pack.success.single: "Sent resource pack to ." +command.send_resource_pack.success.multiple: "Send resource packs to players." warning.config.image.duplicated: "Issue found in file - Duplicated image ''." warning.config.image.lack_height: "Issue found in file - The image '' is missing the required 'height' argument." warning.config.image.height_smaller_than_ascent: "Issue found in file - The image '' violates the bitmap image rule: 'height' should be no lower than 'ascent'." From 50d0bb086d62d7e2db990c048d4115f3c1065c6c Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 19:11:46 +0800 Subject: [PATCH 56/62] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E5=8C=85=E6=9E=84=E9=80=A0=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../default/configuration/furniture.yml | 11 ++--- .../bukkit/entity/data/EntityDataValue.java | 9 ++-- .../furniture/BukkitFurnitureElement.java | 19 ++++---- .../furniture/BukkitFurnitureManager.java | 6 --- .../entity/furniture/LoadedFurniture.java | 12 +++-- .../entity/furniture/hitbox/CustomHitBox.java | 6 +-- .../furniture/hitbox/HappyGhastHitBox.java | 3 +- .../furniture/hitbox/InteractionHitBox.java | 18 ++++---- .../craftengine/bukkit/util/Reflections.java | 46 +++++++++---------- .../entity/furniture/CustomFurniture.java | 1 - .../entity/furniture/FurnitureElement.java | 2 +- .../core/entity/furniture/HitBox.java | 8 +--- gradle.properties | 2 +- 13 files changed, 62 insertions(+), 81 deletions(-) diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml b/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml index d1f429603..2d9b95f97 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml @@ -30,16 +30,13 @@ items: translation: 0,0.5,0 hitboxes: - position: 0,0,0 - width: 1 - height: 1 + type: shulker + direction: east + peek: 100 interactive: true + interaction-entity: true seats: - 0,0,-0.1 0 - - position: 1,0,0 - width: 1 - height: 1 - interactive: true - seats: - 1,0,-0.1 0 loot: template: "default:loot_table/basic" diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityDataValue.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityDataValue.java index c5db7a6e3..bb4c0c782 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityDataValue.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/data/EntityDataValue.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.entity.data; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.VersionHelper; @@ -98,11 +99,7 @@ public class EntityDataValue { } public static Object create(int id, Object serializer, Object value) { - try { - Object entityDataAccessor = Reflections.constructor$EntityDataAccessor.newInstance(id, serializer); - return Reflections.method$SynchedEntityData$DataValue$create.invoke(null, entityDataAccessor, value); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + Object entityDataAccessor = FastNMS.INSTANCE.constructor$EntityDataAccessor(id, serializer); + return FastNMS.INSTANCE.method$SynchedEntityData$DataValue$create(entityDataAccessor, value); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java index b310cacfb..0a4737c20 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.entity.furniture; import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.entity.furniture.AbstractFurnitureElement; import net.momirealms.craftengine.core.entity.furniture.Billboard; @@ -33,17 +34,13 @@ public class BukkitFurnitureElement extends AbstractFurnitureElement { } @Override - public void addSpawnPackets(int entityId, double x, double y, double z, float yaw, Quaternionf conjugated, Consumer packets) { - try { - Vector3f offset = conjugated.transform(new Vector3f(position())); - packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( - entityId, UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw, - Reflections.instance$EntityType$ITEM_DISPLAY, 0, Reflections.instance$Vec3$Zero, 0 - )); - packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId, getCachedValues())); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to construct element spawn packet", e); - } + public void initPackets(int entityId, double x, double y, double z, float yaw, Quaternionf conjugated, Consumer packets) { + Vector3f offset = conjugated.transform(new Vector3f(position())); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityId, UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw, + Reflections.instance$EntityType$ITEM_DISPLAY, 0, Reflections.instance$Vec3$Zero, 0 + )); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId, getCachedValues())); } private synchronized List getCachedValues() { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java index 7b8941a43..1521edffc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java @@ -143,9 +143,6 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { elements.add(furnitureElement); } - // add colliders - List colliders = new ArrayList<>(); - // external model providers Optional externalModel; if (placementArguments.containsKey("model-engine")) { @@ -162,7 +159,6 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { for (Map config : hitboxConfigs) { HitBox hitBox = HitBoxTypes.fromMap(config); hitboxes.add(hitBox); - hitBox.optionalCollider().ifPresent(colliders::add); } if (hitboxes.isEmpty() && externalModel.isEmpty()) { hitboxes.add(InteractionHitBox.DEFAULT); @@ -180,7 +176,6 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { placements.put(anchorType, new CustomFurniture.Placement( elements.toArray(new FurnitureElement[0]), hitboxes.toArray(new HitBox[0]), - colliders.toArray(new Collider[0]), rotationRule, alignmentRule, externalModel @@ -189,7 +184,6 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { placements.put(anchorType, new CustomFurniture.Placement( elements.toArray(new FurnitureElement[0]), hitboxes.toArray(new HitBox[0]), - colliders.toArray(new Collider[0]), RotationRule.ANY, AlignmentRule.CENTER, externalModel diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java index fa5919308..86a73c16b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java @@ -90,10 +90,12 @@ public class LoadedFurniture implements Furniture { List packets = new ArrayList<>(); List minimizedPackets = new ArrayList<>(); + List colliders = new ArrayList<>(); + for (FurnitureElement element : placement.elements()) { int entityId = Reflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); fakeEntityIds.add(entityId); - element.addSpawnPackets(entityId, x, y, z, yaw, conjugated, packet -> { + element.initPackets(entityId, x, y, z, yaw, conjugated, packet -> { packets.add(packet); if (this.minimized) minimizedPackets.add(packet); }); @@ -105,12 +107,12 @@ public class LoadedFurniture implements Furniture { mainEntityIds.add(entityId); this.hitBoxes.put(entityId, hitBox); } - hitBox.addSpawnPackets(ids, x, y, z, yaw, conjugated, (packet, canBeMinimized) -> { + hitBox.initPacketsAndColliders(ids, x, y, z, yaw, conjugated, (packet, canBeMinimized) -> { packets.add(packet); if (this.minimized && !canBeMinimized) { minimizedPackets.add(packet); } - }); + }, colliders::add); } try { this.cachedSpawnPacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets); @@ -122,12 +124,12 @@ public class LoadedFurniture implements Furniture { } this.fakeEntityIds = fakeEntityIds; this.entityIds = mainEntityIds; - int colliderSize = placement.colliders().length; + int colliderSize = colliders.size(); this.collisionEntities = new CollisionEntity[colliderSize]; if (colliderSize != 0) { Object world = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(this.location.getWorld()); for (int i = 0; i < colliderSize; i++) { - Collider collider = placement.colliders()[i]; + Collider collider = colliders.get(i); Vector3f offset = conjugated.transform(new Vector3f(collider.position())); Vector3d offset1 = collider.point1(); Vector3d offset2 = collider.point2(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java index f3355e535..df0452f58 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java @@ -48,14 +48,14 @@ public class CustomHitBox extends AbstractHitBox { } @Override - public void addSpawnPackets(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets) { + public void initPacketsAndColliders(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets, Consumer collider) { Vector3f offset = conjugated.transform(new Vector3f(position())); try { - packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( entityId[0], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw, FastNMS.INSTANCE.toNMSEntityType(this.entityType), 0, Reflections.instance$Vec3$Zero, 0 ), true); - packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId[0], List.copyOf(this.cachedValues)), true); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId[0], List.copyOf(this.cachedValues)), true); if (VersionHelper.isVersionNewerThan1_20_5() && this.scale != 1) { Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Holder$Attribute$scale, (Consumer) (o) -> {}); Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java index e87230357..adb0d4944 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/HappyGhastHitBox.java @@ -8,6 +8,7 @@ import org.joml.Vector3f; import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Supplier; public class HappyGhastHitBox extends AbstractHitBox { @@ -29,7 +30,7 @@ public class HappyGhastHitBox extends AbstractHitBox { } @Override - public void addSpawnPackets(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets) { + public void initPacketsAndColliders(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets, Consumer collider) { // todo 乐魂 } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java index a1daf61e9..a00af2460 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/InteractionHitBox.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.entity.furniture.hitbox; import net.momirealms.craftengine.bukkit.entity.data.InteractionEntityData; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.util.Key; @@ -13,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Supplier; public class InteractionHitBox extends AbstractHitBox { @@ -46,17 +48,13 @@ public class InteractionHitBox extends AbstractHitBox { } @Override - public void addSpawnPackets(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets) { + public void initPacketsAndColliders(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets, Consumer collider) { Vector3f offset = conjugated.transform(new Vector3f(position())); - try { - packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( - entityId[0], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw, - Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 - ), true); - packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityId[0], List.copyOf(this.cachedValues)), true); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to construct interaction hitbox spawn packet", e); - } + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityId[0], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw, + Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 + ), true); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId[0], List.copyOf(this.cachedValues)), true); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index bb3b4ba7b..5d51c0923 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -625,15 +625,15 @@ public class Reflections { ) ); - public static final Constructor constructor$ClientboundAddEntityPacket = requireNonNull( - ReflectionUtils.getConstructor(clazz$ClientboundAddEntityPacket, - int.class, UUID.class, - double.class, double.class, double.class, - float.class, float.class, - clazz$EntityType, - int.class, clazz$Vec3, double.class - ) - ); +// public static final Constructor constructor$ClientboundAddEntityPacket = requireNonNull( +// ReflectionUtils.getConstructor(clazz$ClientboundAddEntityPacket, +// int.class, UUID.class, +// double.class, double.class, double.class, +// float.class, float.class, +// clazz$EntityType, +// int.class, clazz$Vec3, double.class +// ) +// ); public static final Constructor constructor$ClientboundRemoveEntitiesPacket = requireNonNull( ReflectionUtils.getConstructor(clazz$ClientboundRemoveEntitiesPacket, int[].class) @@ -685,10 +685,10 @@ public class Reflections { ) ); - public static final Constructor constructor$ClientboundSetEntityDataPacket = requireNonNull( - ReflectionUtils.getConstructor(clazz$ClientboundSetEntityDataPacket, - int.class, List.class) - ); +// public static final Constructor constructor$ClientboundSetEntityDataPacket = requireNonNull( +// ReflectionUtils.getConstructor(clazz$ClientboundSetEntityDataPacket, +// int.class, List.class) +// ); public static final Class clazz$EntityDataSerializers = requireNonNull( ReflectionUtils.getClazz( @@ -711,11 +711,11 @@ public class Reflections { ) ); - public static final Constructor constructor$EntityDataAccessor = requireNonNull( - ReflectionUtils.getConstructor( - clazz$EntityDataAccessor, int.class, clazz$EntityDataSerializer - ) - ); +// public static final Constructor constructor$EntityDataAccessor = requireNonNull( +// ReflectionUtils.getConstructor( +// clazz$EntityDataAccessor, int.class, clazz$EntityDataSerializer +// ) +// ); public static final Class clazz$SynchedEntityData = requireNonNull( ReflectionUtils.getClazz( @@ -737,11 +737,11 @@ public class Reflections { ) ); - public static final Method method$SynchedEntityData$DataValue$create = requireNonNull( - ReflectionUtils.getMethod( - clazz$SynchedEntityData$DataValue, clazz$SynchedEntityData$DataValue, clazz$EntityDataAccessor, Object.class - ) - ); +// public static final Method method$SynchedEntityData$DataValue$create = requireNonNull( +// ReflectionUtils.getMethod( +// clazz$SynchedEntityData$DataValue, clazz$SynchedEntityData$DataValue, clazz$EntityDataAccessor, Object.class +// ) +// ); public static final Method method$Component$empty = requireNonNull( ReflectionUtils.getStaticMethod( diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java index b0b59d4dd..1b8679ae6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java @@ -58,7 +58,6 @@ public class CustomFurniture { public record Placement(FurnitureElement[] elements, HitBox[] hitBoxes, - Collider[] colliders, RotationRule rotationRule, AlignmentRule alignmentRule, Optional externalModel) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java index 0f048e9bf..340a057e6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java @@ -21,5 +21,5 @@ public interface FurnitureElement { Vector3f position(); - void addSpawnPackets(int entityId, double x, double y, double z, float yaw, Quaternionf conjugated, Consumer packets); + void initPackets(int entityId, double x, double y, double z, float yaw, Quaternionf conjugated, Consumer packets); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBox.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBox.java index 2dc98a150..8a9923f3e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBox.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/HitBox.java @@ -4,23 +4,19 @@ import net.momirealms.craftengine.core.util.Key; import org.joml.Quaternionf; import org.joml.Vector3f; -import java.util.Optional; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Supplier; public interface HitBox { Key type(); - void addSpawnPackets(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets); + void initPacketsAndColliders(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets, Consumer collider); int[] acquireEntityIds(Supplier entityIdSupplier); Seat[] seats(); Vector3f position(); - - default Optional optionalCollider() { - return Optional.empty(); - } } diff --git a/gradle.properties b/gradle.properties index 4af98939e..0336139b6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -51,7 +51,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.13 -nms_helper_version=0.59.6 +nms_helper_version=0.59.7 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 amazon_awssdk_eventstream_version=1.0.1 From 79b109eb7b6a276df0968dee89e828502381cc58 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 19:11:49 +0800 Subject: [PATCH 57/62] Update ShulkerHitBox.java --- .../furniture/hitbox/ShulkerHitBox.java | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java index 793487771..7ad58dff3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java @@ -41,7 +41,7 @@ public class ShulkerHitBox extends AbstractHitBox { ShulkerData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedShulkerValues); ShulkerData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedShulkerValues); ShulkerData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedShulkerValues); // 无ai - ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // 不可见 +// ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // 不可见 float shulkerHeight = (getPhysicalPeek(peek * 0.01F) + 1) * scale; @@ -59,8 +59,7 @@ public class ShulkerHitBox extends AbstractHitBox { } } - @Override - public Optional optionalCollider() { + public Collider createCollider(Direction d) { float peek = getPhysicalPeek(this.peek() * 0.01F); double x1 = -this.scale * 0.5; double y1 = 0.0; @@ -69,30 +68,30 @@ public class ShulkerHitBox extends AbstractHitBox { double y2 = this.scale; double z2 = this.scale * 0.5; - double dx = (double) this.direction.stepX() * peek * (double) scale; + double dx = (double) d.stepX() * peek * (double) this.scale; if (dx > 0) { x2 += dx; } else if (dx < 0) { x1 += dx; } - double dy = (double) this.direction.stepY() * peek * (double) scale; + double dy = (double) d.stepY() * peek * (double) this.scale; if (dy > 0) { y2 += dy; } else if (dy < 0) { y1 += dy; } - double dz = (double) this.direction.stepZ() * peek * (double) scale; + double dz = (double) d.stepZ() * peek * (double) this.scale; if (dz > 0) { z2 += dz; } else if (dz < 0) { z1 += dz; } - return Optional.of(new Collider( + return new Collider( true, - position, + this.position, new Vector3d(x1, y1, z1), new Vector3d(x2, y2, z2) - )); + ); } private static float getPhysicalPeek(float peek) { @@ -125,22 +124,22 @@ public class ShulkerHitBox extends AbstractHitBox { } @Override - public void addSpawnPackets(int[] entityIds, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets) { + public void initPacketsAndColliders(int[] entityIds, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer packets, Consumer collider) { Vector3f offset = conjugated.transform(new Vector3f(position())); try { double originalY = y + offset.y; double integerPart = Math.floor(originalY); double fractionalPart = originalY - integerPart; double processedY = (fractionalPart >= 0.5) ? integerPart + 1 : originalY; - packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( entityIds[0], UUID.randomUUID(), x + offset.x, originalY, z - offset.z, 0, yaw, Reflections.instance$EntityType$ITEM_DISPLAY, 0, Reflections.instance$Vec3$Zero, 0 ), false); - packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( entityIds[1], UUID.randomUUID(), x + offset.x, processedY, z - offset.z, 0, yaw, Reflections.instance$EntityType$SHULKER, 0, Reflections.instance$Vec3$Zero, 0 ), false); - packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[1], List.copyOf(this.cachedShulkerValues)), false); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.copyOf(this.cachedShulkerValues)), false); // add passengers packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(entityIds[0], entityIds[1]), false); // fix some special occasions @@ -158,25 +157,28 @@ public class ShulkerHitBox extends AbstractHitBox { packets.accept(Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance)), false); } if (this.direction == Direction.UP) { + collider.accept(this.createCollider(this.direction)); if (this.interactionEntity) { - packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw, Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 ), true); - packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[2], List.copyOf(this.cachedInteractionValues)), true); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(this.cachedInteractionValues)), true); } } else if (this.direction == Direction.DOWN) { - packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(Reflections.instance$Direction$UP))), false); + collider.accept(this.createCollider(this.direction)); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(Reflections.instance$Direction$UP))), false); if (this.interactionEntity) { - packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance( + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f + this.yOffset, z - offset.z, 0, yaw, Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 ), true); - packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[2], List.copyOf(this.cachedInteractionValues)), true); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(this.cachedInteractionValues)), true); } } else { - Direction shulkerDirection = getOriginalDirection(this.direction, Direction.fromYaw(yaw)); - packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(shulkerDirection)))), false); + Direction shulkerAnchor = getOriginalDirection(this.direction, Direction.fromYaw(yaw)); + collider.accept(this.createCollider(shulkerAnchor.opposite())); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(shulkerAnchor)))), false); } } catch (ReflectiveOperationException e) { throw new RuntimeException("Failed to construct shulker hitbox spawn packet", e); From 42850376ff1fc23e988aff4c131f9d56eafbc617 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 19:46:05 +0800 Subject: [PATCH 58/62] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=B8=A6=E6=96=B9?= =?UTF-8?q?=E5=90=91=E7=9A=84=E6=BD=9C=E5=BD=B1=E8=B4=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/furniture/hitbox/CustomHitBox.java | 1 - .../furniture/hitbox/ShulkerHitBox.java | 108 ++++++++++++------ .../item/behavior/BoneMealItemBehavior.java | 1 - .../bukkit/plugin/BukkitCraftEngine.java | 1 - .../plugin/command/feature/TestCommand.java | 1 - .../core/pack/host/impl/DropboxHost.java | 30 +++-- 6 files changed, 92 insertions(+), 50 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java index df0452f58..fd98180ca 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java @@ -1,7 +1,6 @@ package net.momirealms.craftengine.bukkit.entity.furniture.hitbox; import net.momirealms.craftengine.bukkit.entity.data.BaseEntityData; - import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.entity.furniture.*; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java index 7ad58dff3..4835d991f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java @@ -25,8 +25,7 @@ public class ShulkerHitBox extends AbstractHitBox { private final boolean interactionEntity; private final Direction direction; private final List cachedShulkerValues = new ArrayList<>(); - private final List cachedInteractionValues = new ArrayList<>(); - private final float yOffset; + private final DirectionalShulkerSpawner spawner; public ShulkerHitBox(Seat[] seats, Vector3f position, Direction direction, float scale, byte peek, boolean interactionEntity, boolean interactive) { super(seats, position); @@ -40,22 +39,67 @@ public class ShulkerHitBox extends AbstractHitBox { ShulkerData.Color.addEntityDataIfNotDefaultValue((byte) 0, this.cachedShulkerValues); ShulkerData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedShulkerValues); ShulkerData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedShulkerValues); - ShulkerData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedShulkerValues); // 无ai -// ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // 不可见 + ShulkerData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedShulkerValues); // NO AI + ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // Invisible float shulkerHeight = (getPhysicalPeek(peek * 0.01F) + 1) * scale; - - if (this.interactionEntity) { - // make it a litter bigger + List cachedInteractionValues = new ArrayList<>(); + if (this.direction == Direction.UP) { + Collider c = createCollider(Direction.UP); InteractionEntityData.Height.addEntityDataIfNotDefaultValue(shulkerHeight + 0.01f, cachedInteractionValues); InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues); InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues); - } - - if (this.direction == Direction.DOWN) { - this.yOffset = -shulkerHeight + 1; + this.spawner = (entityIds, x, y, z, yaw, offset, packets, collider) -> { + collider.accept(c); + if (interactionEntity) { + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw, + Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 + ), true); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true); + } + }; + } else if (this.direction == Direction.DOWN) { + Collider c = createCollider(Direction.DOWN); + InteractionEntityData.Height.addEntityDataIfNotDefaultValue(shulkerHeight + 0.01f, cachedInteractionValues); + InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues); + InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues); + this.spawner = (entityIds, x, y, z, yaw, offset, packets, collider) -> { + collider.accept(c); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(Reflections.instance$Direction$UP))), false); + if (interactionEntity) { + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f - shulkerHeight, z - offset.z, 0, yaw, + Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 + ), true); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true); + } + }; } else { - this.yOffset = 0; + InteractionEntityData.Height.addEntityDataIfNotDefaultValue(scale + 0.01f, cachedInteractionValues); + InteractionEntityData.Width.addEntityDataIfNotDefaultValue(scale + 0.005f, cachedInteractionValues); + InteractionEntityData.Responsive.addEntityDataIfNotDefaultValue(interactive, cachedInteractionValues); + this.spawner = (entityIds, x, y, z, yaw, offset, packets, collider) -> { + Direction shulkerAnchor = getOriginalDirection(direction, Direction.fromYaw(yaw)); + Direction shulkerDirection = shulkerAnchor.opposite(); + collider.accept(this.createCollider(shulkerDirection)); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(shulkerAnchor)))), false); + if (interactionEntity) { + // first interaction + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw, + Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 + ), true); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true); + // second interaction + float distance = shulkerHeight - scale; + packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( + entityIds[3], UUID.randomUUID(), x + offset.x + shulkerDirection.stepX() * distance, y + offset.y - 0.005f, z - offset.z + shulkerDirection.stepZ() * distance, 0, yaw, + Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 + ), true); + packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[3], List.copyOf(cachedInteractionValues)), true); + } + }; } } @@ -156,40 +200,28 @@ public class ShulkerHitBox extends AbstractHitBox { Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, this.scale); packets.accept(Reflections.constructor$ClientboundUpdateAttributesPacket0.newInstance(entityIds[1], Collections.singletonList(attributeInstance)), false); } - if (this.direction == Direction.UP) { - collider.accept(this.createCollider(this.direction)); - if (this.interactionEntity) { - packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( - entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f, z - offset.z, 0, yaw, - Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 - ), true); - packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(this.cachedInteractionValues)), true); - } - } else if (this.direction == Direction.DOWN) { - collider.accept(this.createCollider(this.direction)); - packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(Reflections.instance$Direction$UP))), false); - if (this.interactionEntity) { - packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( - entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f + this.yOffset, z - offset.z, 0, yaw, - Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 - ), true); - packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(this.cachedInteractionValues)), true); - } - } else { - Direction shulkerAnchor = getOriginalDirection(this.direction, Direction.fromYaw(yaw)); - collider.accept(this.createCollider(shulkerAnchor.opposite())); - packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(shulkerAnchor)))), false); - } + this.spawner.accept(entityIds, x, y, z, yaw, offset, packets, collider); } catch (ReflectiveOperationException e) { throw new RuntimeException("Failed to construct shulker hitbox spawn packet", e); } } + @FunctionalInterface + interface DirectionalShulkerSpawner { + + void accept(int[] entityIds, double x, double y, double z, float yaw, Vector3f offset, BiConsumer packets, Consumer collider); + } + @Override public int[] acquireEntityIds(Supplier entityIdSupplier) { if (this.interactionEntity) { - // 展示实体 // 潜影贝 // 交互实体 - return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()}; + if (this.direction.stepY() != 0) { + // 展示实体 // 潜影贝 // 交互实体 + return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()}; + } else { + // 展示实体 // 潜影贝 // 交互实体1 // 交互实体2 + return new int[] {entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get(), entityIdSupplier.get()}; + } } else { // 展示实体 // 潜影贝 return new int[] {entityIdSupplier.get(), entityIdSupplier.get()}; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java index 85b629dca..fc08fb7c6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java @@ -16,7 +16,6 @@ import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.block.Block; import java.nio.file.Path; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java index 4825024e3..891850e23 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java @@ -43,7 +43,6 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Pose; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java index 9804bb208..cafb24cdf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java @@ -5,7 +5,6 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.bukkit.entity.Pose; import org.incendo.cloud.Command; public class TestCommand extends BukkitCommandFeature { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java index 814244b9b..f1451fa36 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -1,16 +1,30 @@ package net.momirealms.craftengine.core.pack.host.impl; -import com.google.gson.*; -import net.momirealms.craftengine.core.pack.host.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.*; -import java.io.*; -import java.net.*; -import java.net.http.*; +import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.HashUtils; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.net.ProxySelector; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.nio.file.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.ReentrantLock; public class DropboxHost implements ResourcePackHost { From 32cde7438dc6e6a0b32a52275fdf24cf7bad9167 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 20:16:20 +0800 Subject: [PATCH 59/62] beta4 --- bukkit/build.gradle.kts | 2 +- bukkit/loader/build.gradle.kts | 2 +- gradle.properties | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bukkit/build.gradle.kts b/bukkit/build.gradle.kts index adbf3432f..aba60a7a3 100644 --- a/bukkit/build.gradle.kts +++ b/bukkit/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { compileOnly(project(":bukkit:compatibility")) compileOnly(project(":bukkit:legacy")) // Anti Grief - compileOnly("com.github.Xiao-MoMi:AntiGriefLib:${rootProject.properties["anti_grief_version"]}") + compileOnly("net.momirealms:antigrieflib:${rootProject.properties["anti_grief_version"]}") // NBT compileOnly("net.momirealms:sparrow-nbt:${rootProject.properties["sparrow_nbt_version"]}") compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index b6e21dd81..d228f285e 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { implementation("net.kyori:adventure-platform-bukkit:${rootProject.properties["adventure_platform_version"]}") implementation("com.saicone.rtag:rtag-item:${rootProject.properties["rtag_version"]}") implementation("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") - implementation("com.github.Xiao-MoMi:AntiGriefLib:${rootProject.properties["anti_grief_version"]}") + implementation("net.momirealms:antigrieflib:${rootProject.properties["anti_grief_version"]}") implementation("net.momirealms:craft-engine-nms-helper:${rootProject.properties["nms_helper_version"]}") } diff --git a/gradle.properties b/gradle.properties index f23dccdb3..679d4e46e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.49-beta.3 +project_version=0.0.49-beta.4 config_version=29 lang_version=6 project_group=net.momirealms @@ -50,7 +50,7 @@ mojang_brigadier_version=1.0.18 byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 -anti_grief_version=0.13 +anti_grief_version=0.15 nms_helper_version=0.59.7 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 From 107c95e0894feb316ad3ccc8de1df10238281a2e Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 20:49:13 +0800 Subject: [PATCH 60/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8Ddown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/entity/furniture/hitbox/ShulkerHitBox.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java index 4835d991f..b7be758e4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java @@ -69,7 +69,7 @@ public class ShulkerHitBox extends AbstractHitBox { packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[1], List.of(ShulkerData.AttachFace.createEntityDataIfNotDefaultValue(Reflections.instance$Direction$UP))), false); if (interactionEntity) { packets.accept(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket( - entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f - shulkerHeight, z - offset.z, 0, yaw, + entityIds[2], UUID.randomUUID(), x + offset.x, y + offset.y - 0.005f - shulkerHeight + scale, z - offset.z, 0, yaw, Reflections.instance$EntityType$INTERACTION, 0, Reflections.instance$Vec3$Zero, 0 ), true); packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityIds[2], List.copyOf(cachedInteractionValues)), true); From 986f52b54c85c8e02b39e477089041b83c85db92 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 23:17:09 +0800 Subject: [PATCH 61/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=91=86=E6=94=BE?= =?UTF-8?q?=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/default/configuration/furniture.yml | 2 +- .../bukkit/block/behavior/BushBlockBehavior.java | 8 ++++---- .../bukkit/block/behavior/CropBlockBehavior.java | 2 +- .../bukkit/block/behavior/HangingBlockBehavior.java | 2 +- .../bukkit/block/behavior/SaplingBlockBehavior.java | 2 +- .../bukkit/block/behavior/SugarCaneBlockBehavior.java | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml b/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml index 2d9b95f97..26254e70f 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/furniture.yml @@ -19,7 +19,7 @@ items: ground: rules: # ANY / FOUR / EIGHT / SIXTEEN / NORTH / EAST / WEST / SOUTH - rotation: EIGHT + rotation: FOUR # ANY / CENTER / HALF / QUARTER / CORNER alignment: CENTER elements: diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java index 316912626..9f626c36e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java @@ -96,19 +96,19 @@ public class BushBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Tuple, Set, Set> tuple = readTagsAndState(arguments); + Tuple, Set, Set> tuple = readTagsAndState(arguments, false); return new BushBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right()); } } - public static Tuple, Set, Set> readTagsAndState(Map arguments) { + public static Tuple, Set, Set> readTagsAndState(Map arguments, boolean aboveOrBelow) { List mcTags = new ArrayList<>(); - for (String tag : MiscUtils.getAsStringList(arguments.getOrDefault("bottom-block-tags", List.of()))) { + for (String tag : MiscUtils.getAsStringList(arguments.getOrDefault((aboveOrBelow ? "above" : "bottom") + "-block-tags", List.of()))) { mcTags.add(BlockTags.getOrCreate(Key.of(tag))); } Set mcBlocks = new HashSet<>(); Set customBlocks = new HashSet<>(); - for (String blockStateStr : MiscUtils.getAsStringList(arguments.getOrDefault("bottom-blocks", List.of()))) { + for (String blockStateStr : MiscUtils.getAsStringList(arguments.getOrDefault((aboveOrBelow ? "above" : "bottom") + "-blocks", List.of()))) { int index = blockStateStr.indexOf('['); Key blockType = index != -1 ? Key.from(blockStateStr.substring(0, index)) : Key.from(blockStateStr); Material material = Registry.MATERIAL.get(new NamespacedKey(blockType.namespace(), blockType.value())); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java index 9a97404e7..1141f6815 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java @@ -167,7 +167,7 @@ public class CropBlockBehavior extends BushBlockBehavior { @SuppressWarnings("unchecked") @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Tuple, Set, Set> tuple = readTagsAndState(arguments); + Tuple, Set, Set> tuple = readTagsAndState(arguments, false); Property ageProperty = (Property) block.getProperty("age"); if (ageProperty == null) { throw new IllegalArgumentException("age property not set for crop"); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java index 2d802ed2b..7366aad9d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java @@ -31,7 +31,7 @@ public class HangingBlockBehavior extends BushBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Tuple, Set, Set> tuple = readTagsAndState(arguments); + Tuple, Set, Set> tuple = readTagsAndState(arguments, true); return new HangingBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right()); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java index eed9879f0..666114c6e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java @@ -149,7 +149,7 @@ public class SaplingBlockBehavior extends BushBlockBehavior { throw new IllegalArgumentException("stage property not set for sapling"); } double boneMealSuccessChance = MiscUtils.getAsDouble(arguments.getOrDefault("bone-meal-success-chance", 0.45)); - Tuple, Set, Set> tuple = readTagsAndState(arguments); + Tuple, Set, Set> tuple = readTagsAndState(arguments, false); return new SaplingBlockBehavior(block, Key.of(feature), stageProperty, tuple.left(), tuple.mid(), tuple.right(), boneMealSuccessChance, MiscUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1.0 / 7.0))); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java index d00f514cd..051a0f296 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java @@ -200,7 +200,7 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior { @SuppressWarnings("unchecked") @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Tuple, Set, Set> tuple = readTagsAndState(arguments); + Tuple, Set, Set> tuple = readTagsAndState(arguments, false); Property ageProperty = (Property) block.getProperty("age"); if (ageProperty == null) { throw new IllegalArgumentException("age property not set for sugar cane"); From 6702fccfa302620bb7d7bcbc3908d3150b1996b7 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 19 Apr 2025 23:38:06 +0800 Subject: [PATCH 62/62] =?UTF-8?q?=E4=B8=BA=E4=BB=80=E4=B9=88=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E4=B8=8D=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/remove_shulker_head/pack.yml | 5 +++++ .../textures/entity/shulker/shulker_white.png | Bin 0 -> 1122 bytes .../core/pack/AbstractPackManager.java | 3 +++ 3 files changed, 8 insertions(+) create mode 100644 bukkit/loader/src/main/resources/resources/remove_shulker_head/pack.yml create mode 100644 bukkit/loader/src/main/resources/resources/remove_shulker_head/resourcepack/assets/minecraft/textures/entity/shulker/shulker_white.png diff --git a/bukkit/loader/src/main/resources/resources/remove_shulker_head/pack.yml b/bukkit/loader/src/main/resources/resources/remove_shulker_head/pack.yml new file mode 100644 index 000000000..4dff136be --- /dev/null +++ b/bukkit/loader/src/main/resources/resources/remove_shulker_head/pack.yml @@ -0,0 +1,5 @@ +author: XiaoMoMi +version: 0.0.1 +description: Remove Shulker Head for Some Versions +namespace: minecraft +enable: false \ No newline at end of file diff --git a/bukkit/loader/src/main/resources/resources/remove_shulker_head/resourcepack/assets/minecraft/textures/entity/shulker/shulker_white.png b/bukkit/loader/src/main/resources/resources/remove_shulker_head/resourcepack/assets/minecraft/textures/entity/shulker/shulker_white.png new file mode 100644 index 0000000000000000000000000000000000000000..21e5e1a018133857fe3f1a4918ca75ab6944fe4d GIT binary patch literal 1122 zcmV-o1fBbdP)Dzudv0;&FJgwrmL*a)z+)AvhMNm(%9LkuCBMe zzOJ;jvbed}-QM8k<d6W%+lA`i_@$ zT+ENe0000fbW%=J|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0 z08;cIhX4QrNJ&INRCwC#R@;)JI1CjAj6<4`Cim{$oplWI{Qnp3kVnfC8f)Lb zEyoHO(Sj8-{3U}J&+c>2E-jSEHfq>yQ^`nDlsJ%)VGhU`60+<$et!0n5hs~Zl1vEU zjY_f;_csGRu}1w}4Ii9DGDx>+8aV7(4e~a~h!_Qs4K*3$ZEADWpfP?sG({Ho{6%h@ zO$Ygi)<(@{viP$Z6&atlIGvg7(yy|CY{me`Py@pg;t4fyyq!gmK1pX85o$7JctRh1 zFij5IM#E=v_z=070({U7yO{hxNdNYeb?-^m-2*+njgQo|fevkZm3a+7cbMX@1Hd@M zJiY_)_yt0Id9RPZ2B3{2#(x2z3&nUPgYLx)UIpk&xEi2+v54J*WBjF&u6-LFzS&5> z>BR2ER{lcY16X{W6G{RN$^;6kYntRFsWC>mp8O*KD`us}14tmwWnyvQ$drF92dD0}eC>_)$rM(N5(Bn+bO2Mwjgl|0*ORM)lXF z{JYfHao7>_zq-7ZVrN0D81^gabIa{p;0k=ZOtSF(BJ+Ez0-s@r-{I{r+Np%)4Fk{X zxjmO