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/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..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 @@ -1,24 +1,106 @@ 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.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 boolean isFAWE; - static { + 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); } - public static void register(Key id) throws ReflectiveOperationException { + 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 final class CEBlockParser extends InputParser { + + private 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); + 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 + ":"), + BlockStateParser.fillSuggestions(input).stream() + ); + } + return BlockStateParser.fillSuggestions(input).stream(); + } + + @Override + public BaseBlock parseFromInput(String input, ParserContext context) { + if (isFAWE) { + int index = input.indexOf("["); + if (input.charAt(index+1) == ']') return null; + } + + 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/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index 49c1cecf5..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"]}") } @@ -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/commands.yml b/bukkit/loader/src/main/resources/commands.yml index 81f4a1076..be61cd3c6 100644 --- a/bukkit/loader/src/main/resources/commands.yml +++ b/bukkit/loader/src/main/resources/commands.yml @@ -16,6 +16,20 @@ reload: - /craftengine reload - /ce reload +upload: + enable: true + permission: ce.command.admin.upload + usage: + - /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/config.yml b/bukkit/loader/src/main/resources/config.yml index 7883f4776..91ef6eea5 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -65,29 +65,31 @@ 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. - 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 + hosting: + - type: "self" + ip: "localhost" + port: 8163 + protocol: "http" + deny-non-minecraft-request: true + one-time-token: true + 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/loader/src/main/resources/craft-engine.properties b/bukkit/loader/src/main/resources/craft-engine.properties index 96d51b50e..5901f43dd 100644 --- a/bukkit/loader/src/main/resources/craft-engine.properties +++ b/bukkit/loader/src/main/resources/craft-engine.properties @@ -28,4 +28,8 @@ 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-eventstream=${amazon_awssdk_eventstream_version} \ No newline at end of file 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..26254e70f 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: "" @@ -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: @@ -30,23 +30,20 @@ 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" arguments: item: default:bench default:table_lamp: - material: paper + material: nether_brick custom-model-data: 2001 data: item-name: "" @@ -90,7 +87,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/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 000000000..21e5e1a01 Binary files /dev/null and b/bukkit/loader/src/main/resources/resources/remove_shulker_head/resourcepack/assets/minecraft/textures/entity/shulker/shulker_white.png differ diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index eee50f283..ddef3454c 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -59,6 +59,10 @@ 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." +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 0f03c5226..21921cb41 100644 --- a/bukkit/loader/src/main/resources/translations/zh_cn.yml +++ b/bukkit/loader/src/main/resources/translations/zh_cn.yml @@ -59,6 +59,10 @@ 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: "已开始上传进程. 检查控制台以获取详细信息." +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/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index a2e40a287..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 @@ -87,7 +87,6 @@ public class BukkitBlockManager extends AbstractBlockManager { // Event listeners private final BlockEventListener blockEventListener; private final FallingBlockRemoveListener fallingBlockRemoveListener; - private WorldEditCommandHelper weCommandHelper; public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin); @@ -128,18 +127,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(); } } @@ -159,7 +151,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 @@ -181,13 +172,14 @@ public class BukkitBlockManager extends AbstractBlockManager { } public void initFastAsyncWorldEditHook() { - // do nothing + new WorldEditBlockRegister(this, true); } public void initWorldEditHook() { + WorldEditBlockRegister weBlockRegister = new WorldEditBlockRegister(this, false); 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); 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; - } - } -} 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 4dd7721ce..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 @@ -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; } @@ -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 5bfeab461..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 @@ -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; } @@ -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"); 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 24c43664f..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,13 +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++) { - // TODO better shulker hitbox - 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/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..fd98180ca --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/CustomHitBox.java @@ -0,0 +1,86 @@ +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 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(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(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); + 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/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 d895dfb88..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 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/entity/furniture/hitbox/ShulkerHitBox.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/hitbox/ShulkerHitBox.java index 583bdef6b..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 @@ -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,13 @@ 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 DirectionalShulkerSpawner spawner; - 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,22 +37,73 @@ 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); // 不可见 + ShulkerData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedShulkerValues); // NO AI + ShulkerData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, this.cachedShulkerValues); // Invisible - if (this.interactionEntity) { - // make it a litter bigger - InteractionEntityData.Height.addEntityDataIfNotDefaultValue((getPhysicalPeek(peek * 0.01F) + 1) * scale + 0.01f, cachedInteractionValues); + float shulkerHeight = (getPhysicalPeek(peek * 0.01F) + 1) * scale; + 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); + 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 + 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); + } + }; + } else { + 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); + } + }; } } - @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; @@ -60,30 +112,30 @@ 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) d.stepX() * peek * (double) this.scale; if (dx > 0) { x2 += dx; } else if (dx < 0) { x1 += dx; } - double dy = (double) 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) 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) { @@ -116,23 +168,25 @@ 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 if (originalY != processedY) { double deltaY = originalY - processedY; short ya = (short) (deltaY * 8192); @@ -140,29 +194,34 @@ public class ShulkerHitBox extends AbstractHitBox { entityIds[1], (short) 0, ya, (short) 0, true ), false); } - if (VersionHelper.isVersionNewerThan1_20_5()) { + // 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, scale); + 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); - } + 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()}; @@ -181,9 +240,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/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..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.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/item/behavior/BoneMealItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java index fcac2aa5c..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 @@ -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; @@ -51,7 +52,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/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 6a875cb5a..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 @@ -1,6 +1,5 @@ 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; @@ -8,236 +7,162 @@ 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.HostMode; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; +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.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.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 { - private final BukkitCraftEngine plugin; - private HostMode previousHostMode = HostMode.NONE; - private UUID previousHostUUID; + public static final String FAKE_URL = "https://127.0.0.1:65536"; + 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) { - // for 1.20.1 servers, not recommended to use - if (Config.sendPackOnJoin() && !VersionHelper.isVersionNewerThan1_20_2()) { - this.sendResourcePack(plugin.networkManager().getUser(event.getPlayer()), null); - } - } + @EventHandler(priority = EventPriority.LOW) + public void onPlayerJoin(PlayerJoinEvent event) { + if (Config.sendPackOnJoin() && !VersionHelper.isVersionNewerThan1_20_2()) { + Player player = plugin.adapt(event.getPlayer()); + this.sendResourcePack(player); + } + } - @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 (ReloadCommand.RELOAD_PACK_FLAG || CraftEngine.instance().isInitializing()) { + super.load(); + if (Config.sendPackOnJoin() && VersionHelper.isVersionNewerThan1_20_2() && !(resourcePackHost() instanceof NoneHost)) { + this.modifyServerSettings(); + } + } + } - // 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 - public void disable() { - super.disable(); - HandlerList.unregisterAll(this); - } - - @Override - 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); + 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(uuid, url, sha1, required, ComponentUtils.adventureToMinecraft(prompt)); + info = Reflections.constructor$ServerResourcePackInfo.newInstance(new UUID(0, 0), FAKE_URL, "", Config.kickOnDeclined(), ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt())); } else { - info = Reflections.constructor$ServerResourcePackInfo.newInstance(url + uuid, sha1, required, ComponentUtils.adventureToMinecraft(prompt)); + 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) { - this.plugin.logger().warn("Failed to update resource pack settings", 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; - } + @Override + public void unload() { + super.unload(); + if (ReloadCommand.RELOAD_PACK_FLAG) { + if (VersionHelper.isVersionNewerThan1_20_2()) { + this.resetServerSettings(); + } + } + } - 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); - } - } + @Override + public void disable() { + super.disable(); + HandlerList.unregisterAll(this); + this.resetServerSettings(); + } - 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); - } - } + 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); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onAsyncResourcePackGenerate(AsyncResourcePackGenerateEvent event) { + if (!Config.autoUpload()) return; + uploadResourcePack(); + } + + @Override + public void uploadResourcePack() { + resourcePackHost().upload(Config.fileToUpload()).whenComplete((d, e) -> { + if (e != null) { + CraftEngine.instance().logger().warn("Failed to upload resource pack", e); + return; + } + if (!Config.sendPackOnUpload()) return; + for (BukkitServerPlayer player : this.plugin.networkManager().onlineUsers()) { + sendResourcePack(player); + } + }); + } + + @Override + public 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); + } + } + }).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/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index 556ceaf64..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 @@ -49,7 +49,9 @@ 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), + 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/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/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..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 @@ -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,13 +48,13 @@ 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); @@ -69,24 +68,30 @@ 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); } }); } 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/command/feature/SendResourcePackCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java new file mode 100644 index 000000000..1a8fe844b --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/SendResourcePackCommand.java @@ -0,0 +1,53 @@ +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; +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); + } + 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)); + } + }); + } + + @Override + public String getFeatureID() { + return "send_resource_pack"; + } +} 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..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 @@ -1,14 +1,10 @@ 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.incendo.cloud.Command; public class TestCommand extends BukkitCommandFeature { @@ -23,10 +19,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/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..1450b37e9 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/UploadPackCommand.java @@ -0,0 +1,36 @@ +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.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); + plugin().packManager().uploadResourcePack(); + } else { + handleFeedback(context, MessageConstants.COMMAND_UPLOAD_FAILURE_NOT_SUPPORTED, Component.text(host.type().value())); + } + }); + } + + @Override + public String getFeatureID() { + return "upload"; + } +} 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..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) { @@ -130,6 +131,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); @@ -144,6 +146,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()); @@ -286,7 +289,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 ec32770cb..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 @@ -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; @@ -21,6 +22,8 @@ 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; @@ -51,6 +54,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]; @@ -1156,6 +1160,28 @@ public class PacketConsumers { } } + public static final TriConsumer HELLO_C2S = (user, event, packet) -> { + try { + BukkitServerPlayer player = (BukkitServerPlayer) user; + String name = (String) Reflections.field$ServerboundHelloPacket$name.get(packet); + player.setName(name); + 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); + } + }; + public static final TriConsumer SWING_HAND = (user, event, packet) -> { try { if (!user.isOnline()) return; @@ -2083,4 +2109,36 @@ 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; + // we should only handle fake urls + String url = FastNMS.INSTANCE.field$ClientboundResourcePackPushPacket$url(packet); + if (!url.equals(BukkitPackManager.FAKE_URL)) { + return; + } + + event.setCancelled(true); + 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()); + } + }).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/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..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,17 +34,18 @@ 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.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; 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; + private final Set resourcePackUUID = Collections.synchronizedSet(new HashSet<>()); // some references private Reference playerRef; private Reference serverPlayerRef; @@ -94,6 +95,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 +222,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 @@ -250,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 @@ -723,9 +741,29 @@ public class BukkitServerPlayer extends Player { this.hasClientMod = enable; } + @Override + public void addResourcePackUUID(UUID uuid) { + if (VersionHelper.isVersionNewerThan1_20_3()) { + this.resourcePackUUID.add(uuid); + } + } + @Override public void clearView() { this.entityTypeView.clear(); this.furnitureView.clear(); } + + @Override + public void unloadCurrentResourcePack() { + 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/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index 0ce455beb..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( @@ -6437,4 +6437,79 @@ 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.login.ServerboundHelloPacket"), + BukkitReflectionUtils.assembleMCClass("network.protocol.login.PacketLoginInStart") + ) + ); + + public static final Field field$ServerboundHelloPacket$name = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$ServerboundHelloPacket, String.class, 0 + ) + ); + + 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 + ) + ); + + 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..8d15638b3 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ResourcePackUtils.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.bukkit.util; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.core.plugin.config.Config; + +import java.util.UUID; + +public class ResourcePackUtils { + + 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/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 diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 896447ad4..0ba9d3b0a 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -51,6 +51,9 @@ 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"]}") + compileOnly("software.amazon.awssdk:netty-nio-client:${rootProject.properties["amazon_awssdk_version"]}") } java { @@ -80,6 +83,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/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 { 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/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/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/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/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/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/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index bcc2ffa4a..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.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 7b6ed87a1..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.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 b8ff6f265..2171e289e 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,10 +7,12 @@ 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.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; 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; @@ -34,10 +36,7 @@ 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.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; @@ -68,10 +67,8 @@ 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; + protected ResourcePackHost resourcePackHost; public AbstractPackManager(CraftEngine plugin, BiConsumer eventDispatcher) { this.plugin = plugin; @@ -150,16 +147,19 @@ 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); + List> list = Config.instance().settings().getMapList("resource-pack.delivery.hosting"); + if (list == null || list.isEmpty()) { + this.resourcePackHost = NoneHost.INSTANCE; } else { - ResourcePackHost.instance().disable(); + this.resourcePackHost = ResourcePackHosts.fromMap(MiscUtils.castToMap(list.get(0), false)); } } + @Override + public ResourcePackHost resourcePackHost() { + return this.resourcePackHost; + } + @Override public void loadResources(boolean recipe) { this.loadPacks(); @@ -222,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 { @@ -264,6 +260,9 @@ public abstract class AbstractPackManager implements PackManager { } private void saveDefaultConfigs() { + // internal + plugin.saveResource("resources/remove_shulker_head/resourcepack/assets/minecraft/textures/entity/shulker/shulker_white.png"); + plugin.saveResource("resources/remove_shulker_head/pack.yml"); // internal plugin.saveResource("resources/internal/resourcepack/assets/minecraft/models/block/default_chorus_plant.json"); plugin.saveResource("resources/internal/pack.yml"); @@ -310,6 +309,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"); @@ -526,24 +527,7 @@ public abstract class AbstractPackManager implements PackManager { long end = System.currentTimeMillis(); 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) { @@ -1137,23 +1121,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/PackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/PackManager.java index 54764ce6a..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,7 @@ 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; import org.jetbrains.annotations.NotNull; @@ -33,4 +35,10 @@ public interface PackManager extends Manageable { void generateResourcePack(); Path resourcePackPath(); + + ResourcePackHost resourcePackHost(); + + void uploadResourcePack(); + + void sendResourcePack(Player player); } 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/ResourcePackDownloadData.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java new file mode 100644 index 000000000..9a39f1d70 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackDownloadData.java @@ -0,0 +1,10 @@ +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/ResourcePackHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHost.java index be9052f45..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,139 +1,19 @@ 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 net.momirealms.craftengine.core.util.Key; -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; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; -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 interface ResourcePackHost { - public String url() { - return Config.hostProtocol() + "://" + ip + ":" + port + "/"; - } + CompletableFuture> requestResourcePackDownloadLink(UUID player); - 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; + CompletableFuture upload(Path 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); - } - } + boolean canUpload(); - 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; - } - } + Key type(); } 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..c6207c254 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/ResourcePackHosts.java @@ -0,0 +1,52 @@ +package net.momirealms.craftengine.core.pack.host; + +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; +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 = 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 = 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, SelfHost.FACTORY); + register(EXTERNAL, ExternalHost.FACTORY); + register(LOBFILE, LobFileHost.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) { + 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/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java new file mode 100644 index 000000000..615cd4f05 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -0,0 +1,318 @@ +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; +import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.*; + +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; +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.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 uploadPath; + private final boolean disableUpload; + private final ProxySelector proxy; + private Pair jwtToken; + private String cachedSha1; + + public AlistHost(String apiUrl, + String userName, + String password, + String filePassword, + String otpCode, + Duration jwtTokenExpiration, + String uploadPath, + boolean disableUpload, + ProxySelector proxy) { + this.apiUrl = apiUrl; + this.userName = userName; + this.password = password; + this.filePassword = filePassword; + this.otpCode = otpCode; + this.jwtTokenExpiration = jwtTokenExpiration; + this.uploadPath = uploadPath; + this.disableUpload = disableUpload; + 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; + + try (InputStream is = Files.newInputStream(cachePath)) { + 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, + GsonHelper.get().toJson(cache), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ); + } catch (IOException e) { + CraftEngine.instance().logger().warn( + "[Alist] Failed to persist cache to disk: " + e.getMessage()); + } + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + 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/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 retrieve resource pack download URL", ex); + future.completeExceptionally(ex); + return null; + }); + } + }); + return future; + } + + @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()) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(this.apiUrl + "/api/fs/put")) + .header("Authorization", getOrRefreshJwtToken()) + .header("File-Path", URLEncoder.encode(this.uploadPath, StandardCharsets.UTF_8) + .replace("/", "%2F")) + .header("overwrite", "true") + .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] Initiating resource pack upload..."); + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + long uploadTime = System.currentTimeMillis() - requestStart; + if (response.statusCode() == 200) { + this.cachedSha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); + saveCacheToDisk(); + 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())); + } + }) + .exceptionally(ex -> { + long uploadTime = System.currentTimeMillis() - requestStart; + CraftEngine.instance().logger().severe( + "[Alist] Resource pack upload failed 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 (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(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] Authentication failed (HTTP " + response.statusCode() + "): " + response.body()); + return null; + } + 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"); + if (data.isJsonObject()) { + JsonObject jsonObj = data.getAsJsonObject(); + this.jwtToken = Pair.of( + jsonObj.getAsJsonPrimitive("token").getAsString(), + new Date(System.currentTimeMillis() + this.jwtTokenExpiration.toMillis()) + ); + return this.jwtToken.left(); + } + CraftEngine.instance().logger().warn("[Alist] Invalid JWT response format: " + response.body()); + return null; + } + CraftEngine.instance().logger().warn("[Alist] Authentication rejected: " + response.body()); + return null; + } catch (IOException | InterruptedException e) { + CraftEngine.instance().logger().warn("[Alist] JWT token acquisition failed", e); + return null; + } + } + return this.jwtToken.left(); + } + + private HttpRequest.BodyPublisher getLoginPost() { + 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\":\"" + this.uploadPath + "\",\"password\":\"" + this.filePassword + "\"}"; + return HttpRequest.BodyPublishers.ofString(body); + } + + private void handleResourcePackDownloadLinkResponse( + HttpResponse response, CompletableFuture> future) { + if (response.statusCode() == 200) { + 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"); + if (data.isJsonObject()) { + JsonObject dataObj = data.getAsJsonObject(); + 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; + } + } + } + } + future.completeExceptionally( + new RuntimeException("Failed to obtain resource pack download URL (HTTP " + response.statusCode() + "): " + response.body())); + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + 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 = 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 = 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 = 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"); + 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, disableUpload, 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 new file mode 100644 index 000000000..f1451fa36 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/DropboxHost.java @@ -0,0 +1,286 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +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.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.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; + +public class DropboxHost implements ResourcePackHost { + public static final Factory FACTORY = new Factory(); + 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; + + 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(); + } + + public void readCacheFromDisk() { + Path cachePath = CraftEngine.instance().dataFolderPath().resolve("dropbox.cache"); + if (!Files.exists(cachePath)) return; + + try (InputStream is = Files.newInputStream(cachePath)) { + JsonObject cache = GsonHelper.parseJsonToJsonObject(new String(is.readAllBytes(), StandardCharsets.UTF_8)); + + 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", e); + } + } + + public void saveCacheToDisk() { + 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 { + 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); + } + } + + @Override + public boolean canUpload() { + return true; + } + + @Override + public Key type() { + return ResourcePackHosts.DROPBOX; + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + 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(() -> { + 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); + } + }); + + return future; + } + + private String getDownloadUrl(String accessToken) { + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { + 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(listRequest.toString())) + .build(); + + 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 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) { + 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 = 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 = 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"); + } + String uploadPath = (String) arguments.getOrDefault("upload-path", "resource_pack.zip"); + + ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); + return new DropboxHost(appKey, appSecret, refreshToken, "/" + uploadPath, proxy); + } + } +} \ No newline at end of file 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..563bc1ca9 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/ExternalHost.java @@ -0,0 +1,63 @@ +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; +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); + } + + @Override + public boolean canUpload() { + return false; + } + + @Override + public Key type() { + return ResourcePackHosts.EXTERNAL; + } + + 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/LobFileHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java new file mode 100644 index 000000000..116eac469 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/LobFileHost.java @@ -0,0 +1,310 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +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; +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.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +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 { + public static final Factory FACTORY = new Factory(); + private final String apiKey; + private final ProxySelector proxy; + private AccountInfo accountInfo; + + private String url; + private String sha1; + private UUID uuid; + + public LobFileHost(String apiKey, ProxySelector proxy) { + this.apiKey = apiKey; + this.proxy = proxy; + 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; + + 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() { + if (this.accountInfo == null) return "Usage data not available"; + return String.format("Storage: %d/%d MB (%.1f%% used)", + 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(this.url, this.uuid, this.sha1))); + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + long totalStartTime = System.currentTimeMillis(); + CraftEngine.instance().scheduler().executeAsync(() -> { + try { + Map hashes = calculateHashes(resourcePackPath); + String sha1Hash = hashes.get("SHA-1"); + String sha256Hash = hashes.get("SHA-256"); + + 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", this.apiKey) + .header("Content-Type", "multipart/form-data; boundary=" + boundary) + .POST(buildMultipartBody(resourcePackPath, sha256Hash, boundary)) + .build(); + + long uploadStart = System.currentTimeMillis(); + CraftEngine.instance().logger().info("[LobFile] Starting file upload..."); + + client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .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); + } + }); + return future; + } + + public CompletableFuture fetchAccountInfo() { + 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", this.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()); + }); + } + } + + @SuppressWarnings("all") + 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)) { + 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) { + Map json = GsonHelper.parseJsonToMap(response.body()); + if (Boolean.TRUE.equals(json.get("success"))) { + 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()); + 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"))); + } + } 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(); + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + 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"); + } + ProxySelector proxy = MiscUtils.getProxySelector(arguments.get("proxy")); + return new LobFileHost(apiKey, proxy); + } + } + + @SuppressWarnings({"all"}) + public static class AccountInfo { + private boolean success; + private Map account_info; + private Map account_limits; + private Map account_usage; + + public String getEmail() { + return (String) this.account_info.get("email"); + } + + public int getSpaceQuota() { + return this.account_limits.getOrDefault("space_quota", 0); + } + + public int getSpaceUsed() { + return this.account_usage.getOrDefault("space_used", 0); + } + + public int getSlotsUsed() { + return this.account_usage.getOrDefault("slots_used", 0); + } + + public boolean isSuccess() { + return this.success; + } + } +} \ No newline at end of file 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..3802756b5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/NoneHost.java @@ -0,0 +1,46 @@ +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; +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); + } + + @Override + public boolean canUpload() { + return false; + } + + @Override + public Key type() { + return ResourcePackHosts.NONE; + } + + 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/OneDriveHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java new file mode 100644 index 000000000..3ef7e3002 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/OneDriveHost.java @@ -0,0 +1,254 @@ +package net.momirealms.craftengine.core.pack.host.impl; + +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 net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.*; + +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.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 uploadPath; + private Tuple refreshToken; + private String sha1; + private String fileId; + + public OneDriveHost(String clientId, + String clientSecret, + String refreshToken, + String uploadPath, + ProxySelector proxy) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.proxy = proxy; + this.uploadPath = uploadPath; + this.refreshToken = Tuple.of(refreshToken, "", new Date()); + 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; + + 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 load cache from disk: " + 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 persist cache to disk: " + e.getMessage()); + } + } + + @Override + public CompletableFuture> requestResourcePackDownloadLink(UUID player) { + CompletableFuture> future = new CompletableFuture<>(); + CraftEngine.instance().scheduler().executeAsync(() -> { + 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/" + this.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 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(this.sha1.getBytes(StandardCharsets.UTF_8)), + this.sha1 + ))); + }) + .exceptionally(ex -> { + CraftEngine.instance().logger().severe("[OneDrive] Error retrieving download link: " + ex.getMessage()); + future.completeExceptionally(ex); + return null; + }); + } + }); + return future; + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + CraftEngine.instance().scheduler().executeAsync(() -> { + this.sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); + String accessToken = getOrRefreshJwtToken(); + try (HttpClient client = HttpClient.newBuilder().proxy(this.proxy).build()) { + HttpRequest request = HttpRequest.newBuilder() + .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] 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] Successfully uploaded resource pack in " + elapsedTime + " ms"); + this.fileId = GsonHelper.parseJsonToJsonObject(response.body()).get("id").getAsString(); + saveCacheToDisk(); + future.complete(null); + } else { + 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().severe("[OneDrive] Upload operation failed: " + ex.getMessage()); + future.completeExceptionally(ex); + return null; + }); + } catch (FileNotFoundException e) { + CraftEngine.instance().logger().warn("[OneDrive] Resource pack file not found: " + e.getMessage()); + future.completeExceptionally(e); + } + }); + return future; + } + + private String getOrRefreshJwtToken() { + 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(this.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().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("Authentication error: " + jsonData); + } + long expiresInMillis = jsonData.get("expires_in").getAsInt() * 1000L; + 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().severe("[OneDrive] Token refresh failure: " + e.getMessage()); + throw new RuntimeException("Authentication process failed", e); + } + } + + return this.refreshToken.mid(); + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + public ResourcePackHost create(Map arguments) { + 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 = 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 = 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"); + } + 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, 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 new file mode 100644 index 000000000..8c2137e2a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java @@ -0,0 +1,236 @@ +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; +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; +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; + +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.time.Duration; +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; + +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 cdnDomain; + private final String cdnProtocol; + private final Duration validity; + + public S3Host( + S3AsyncClient s3AsyncClient, + S3Presigner preSigner, + String bucket, + String uploadPath, + String cdnDomain, + String cdnProtocol, + Duration validity + ) { + this.s3AsyncClient = s3AsyncClient; + this.preSigner = preSigner; + this.bucket = bucket; + this.uploadPath = uploadPath; + this.cdnDomain = cdnDomain; + this.cdnProtocol = cdnProtocol; + 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() + .bucket(this.bucket) + .key(this.uploadPath) + .build()) + .handle((headResponse, exception) -> { + if (exception != null) { + Throwable cause = exception.getCause(); + if (cause instanceof NoSuchKeyException) { + 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] Failed to retrieve resource pack metadata. Reason: " + + cause.getClass().getSimpleName() + " - " + cause.getMessage() + ); + 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] 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) + .getObjectRequest(b -> b.bucket(this.bucket).key(this.uploadPath)) + .build(); + return Collections.singletonList( + ResourcePackDownloadData.of( + replaceWithCdnUrl(this.preSigner.presignGetObject(presignRequest).url()), + UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), + sha1 + ) + ); + }); + } + + @Override + public CompletableFuture upload(Path resourcePackPath) { + String sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(this.bucket) + .key(this.uploadPath) + .metadata(Map.of("sha1", sha1)) + .build(); + long uploadStart = System.currentTimeMillis(); + 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) { + Throwable cause = exception instanceof CompletionException ? + exception.getCause() : + exception; + CraftEngine.instance().logger().warn( + "[S3] Upload failed for path '" + this.uploadPath + "'. Error: " + + cause.getClass().getSimpleName() + " - " + cause.getMessage() + ); + throw new CompletionException("Failed to upload to S3 path: " + this.uploadPath, cause); + } + CraftEngine.instance().logger().info( + "[S3] Successfully uploaded resource pack to '" + this.uploadPath + "' in " + + (System.currentTimeMillis() - uploadStart) + " ms" + ); + return null; + }); + } + + private String replaceWithCdnUrl(URL originalUrl) { + if (this.cdnDomain == null) return originalUrl.toString(); + return this.cdnProtocol + "://" + this.cdnDomain + + originalUrl.getPath() + + (originalUrl.getQuery() != null ? "?" + originalUrl.getQuery() : ""); + } + + public static class Factory implements ResourcePackHostFactory { + + @Override + @SuppressWarnings("deprecation") + public ResourcePackHost create(Map arguments) { + 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"); + } + 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 = 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 = 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"); + } + 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"); + } + 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()) + ); + } + + 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 configuration"); + } + 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() + .endpointOverride(URI.create(protocol + "://" + endpoint)) + .region(Region.of(region)) + .credentialsProvider(StaticCredentialsProvider.create(credentials)) + .build(); + + 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/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java new file mode 100644 index 000000000..9febdb22e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -0,0 +1,84 @@ +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; +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() { + SelfHostHttpServer.instance().readResourcePack(Config.fileToUpload()); + } + + @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 upload(Path resourcePackPath) { + CompletableFuture future = new CompletableFuture<>(); + CraftEngine.instance().scheduler().executeAsync(() -> { + try { + SelfHostHttpServer.instance().readResourcePack(resourcePackPath); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + return future; + } + + @Override + public boolean canUpload() { + return true; + } + + @Override + public Key type() { + return ResourcePackHosts.SELF; + } + + 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"); + } + 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); + 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, 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 new file mode 100644 index 000000000..6ce927583 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -0,0 +1,291 @@ +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; +import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +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.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +public class SelfHostHttpServer { + private static SelfHostHttpServer instance; + private final Cache oneTimePackUrls = Caffeine.newBuilder() + .maximumSize(256) + .expireAfterAccess(1, TimeUnit.MINUTES) + .build(); + private final Cache ipAccessCache = Caffeine.newBuilder() + .maximumSize(256) + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + + private ExecutorService threadPool; + private HttpServer server; + + 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 String protocol = "http"; + private boolean denyNonMinecraft = true; + private boolean useToken; + + private volatile byte[] resourcePackBytes; + private String packHash; + private UUID packUUID; + + public void updateProperties(String ip, + int port, + boolean denyNonMinecraft, + String protocol, + int maxRequests, + 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); + } + if (port == this.port && this.server != null) return; + if (this.server != null) disable(); + this.port = port; + try { + 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); + } + } + + public static SelfHostHttpServer instance() { + if (instance == null) { + instance = new SelfHostHttpServer(); + } + return instance; + } + + @Nullable + public ResourcePackDownloadData generateOneTimeUrl() { + if (this.resourcePackBytes == null) { + return null; + } + if (!this.useToken) { + 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), + this.packUUID, + this.packHash + ); + } + + public String url() { + return this.protocol + "://" + this.ip + ":" + this.port + "/"; + } + + public void readResourcePack(Path path) { + try { + if (Files.exists(path)) { + this.resourcePackBytes = Files.readAllBytes(path); + calculateHash(); + } else { + this.resourcePackBytes = null; + } + } catch (IOException e) { + CraftEngine.instance().logger().severe("Failed to load resource pack", e); + } + } + + private void calculateHash() { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(this.resourcePackBytes); + byte[] hashBytes = digest.digest(); + + StringBuilder hexString = new StringBuilder(); + for (byte b : hashBytes) { + hexString.append(String.format("%02x", b)); + } + this.packHash = hexString.toString(); + this.packUUID = UUID.nameUUIDFromBytes(this.packHash.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + CraftEngine.instance().logger().severe("SHA-1 algorithm not available", 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 (this.server != null) { + this.server.stop(0); + this.server = null; + if (this.threadPool != null) { + this.threadPool.shutdownNow(); + } + } + } + + 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; + } + if (useToken) { + 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 (!denyNonMinecraft) 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; + } + } +} \ No newline at end of file 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/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; 写入签名头(上下文, 压缩头验证器.中央目录标记); } 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..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 @@ -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; @@ -70,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__%%"; @@ -192,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); @@ -220,6 +221,7 @@ public abstract class CraftEngine implements Plugin { this.furnitureManager.delayedInit(); // set up some platform extra tasks this.platformDelayedEnable(); + this.isInitializing = false; }); } @@ -243,7 +245,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() { @@ -278,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, @@ -291,7 +291,39 @@ 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_HTTP, + 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 ); } @@ -326,6 +358,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 777cae1d3..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 @@ -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; @@ -30,10 +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.*; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; public class Config { @@ -79,24 +79,13 @@ 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; + 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 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; @@ -215,23 +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$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$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); @@ -240,7 +218,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); @@ -443,10 +420,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; } @@ -459,60 +432,28 @@ 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; - } - - public static int hostPort() { - return instance.resource_pack$send$self_host$port; - } - public static boolean kickOnDeclined() { - return instance.resource_pack$send$kick_if_declined; + return instance.resource_pack$delivery$kick_if_declined; } public static Component resourcePackPrompt() { 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; + 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 int requestRate() { - return instance.resource_pack$send$self_host$rate_limit$max_requests; + public static boolean autoUpload() { + return instance.resource_pack$delivery$auto_upload; } - 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 Path fileToUpload() { + return instance.resource_pack$delivery$file_to_upload; } public static List resolutions() { @@ -757,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/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 6a70db819..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 @@ -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", @@ -228,4 +247,482 @@ public class Dependencies { "lz4-java", 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", + "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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @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", + "software{}amazon{}awssdk", + "http-client-spi", + "amazon-http-client-spi", + List.of( + 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", + "software{}amazon{}awssdk", + "protocol-core", + "amazon-protocol-core", + List.of( + 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", + "software{}amazon{}awssdk", + "aws-xml-protocol", + "amazon-aws-xml-protocol", + List.of( + 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", + "software{}amazon{}awssdk", + "json-utils", + "amazon-json-utils", + List.of( + 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", + "software{}amazon{}awssdk", + "aws-core", + "amazon-aws-core", + List.of( + 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", + "software{}amazon{}awssdk", + "utils", + "amazon-utils", + List.of( + 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", + "software{}amazon{}awssdk", + "annotations", + "amazon-annotations", + List.of( + 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", + "software{}amazon{}awssdk", + "crt-core", + "amazon-crt-core", + List.of( + 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", + "software{}amazon{}awssdk", + "checksums", + "amazon-checksums", + List.of( + 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", + "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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @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", + "software{}amazon{}awssdk", + "aws-query-protocol", + "amazon-aws-query-protocol", + List.of( + 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", + "software{}amazon{}awssdk", + "http-auth-aws", + "amazon-http-auth-aws", + List.of( + 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", + "software{}amazon{}awssdk", + "http-auth-spi", + "amazon-http-auth-spi", + List.of( + 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", + "software{}amazon{}awssdk", + "http-auth", + "amazon-http-auth", + List.of( + 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", + "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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; + + 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") + ) + ) { + @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", + "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") + ) + ) { + @Override + public String getVersion() { + return AMAZON_AWSSDK_S3.getVersion(); + } + }; } \ No newline at end of file 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/plugin/locale/MessageConstants.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java index 3d61517ab..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 @@ -26,4 +26,8 @@ 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"); + 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"); } 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..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 @@ -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,9 +18,15 @@ 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); + void simulatePacket(Object packet); @ApiStatus.Internal ConnectionState decoderState(); @@ -42,4 +49,6 @@ public interface NetWorkUser { boolean clientModEnabled(); void setClientModState(boolean enable); + + 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/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; 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); + } + } +} 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..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,6 +4,8 @@ import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; +import java.net.InetSocketAddress; +import java.net.ProxySelector; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -172,4 +174,19 @@ 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; + } } diff --git a/gradle.properties b/gradle.properties index d7349e4e7..679d4e46e 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.48.1 -config_version=27 -lang_version=4 +project_version=0.0.49-beta.4 +config_version=29 +lang_version=6 project_group=net.momirealms latest_supported_version=1.21.5 latest_minecraft_version=1.21.5 @@ -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 @@ -50,8 +50,11 @@ 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 -nms_helper_version=0.59 +anti_grief_version=0.15 +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 # Ignite Dependencies mixinextras_version=0.4.1 mixin_version=0.15.2+mixin.0.8.7 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;