9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-27 02:49:15 +00:00

Merge pull request #125 from Xiao-MoMi/dev

0.0.49-beta.4
This commit is contained in:
XiaoMoMi
2025-04-20 01:54:56 +08:00
committed by GitHub
95 changed files with 3966 additions and 929 deletions

View File

@@ -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"]}")

View File

@@ -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<BaseBlock> {
private CEBlockParser(WorldEdit worldEdit) {
super(worldEdit);
}
@Override
public Stream<String> getSuggestions(String input) {
Set<String> 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<String> 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;
}
}
}
}

View File

@@ -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")
}
}

View File

@@ -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

View File

@@ -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: "<yellow>To fully experience our server, please accept our custom resource pack.</yellow>"
# 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

View File

@@ -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}
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}

View File

@@ -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

View File

@@ -1,6 +1,6 @@
items#misc:
default:chinese_lantern:
material: paper
material: nether_brick
custom-model-data: 3000
data:
item-name: "<!i><i18n:item.chinese_lantern>"
@@ -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: "<!i><i18n:item.netherite_anvil>"
@@ -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: "<!i><i18n:item.gunpowder_block>"
@@ -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: "<!i><i18n:item.solid_gunpowder_block>"

View File

@@ -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"

View File

@@ -1,6 +1,6 @@
items:
default:bench:
material: paper
material: nether_brick
custom-model-data: 2000
data:
item-name: "<!i><i18n:item.bench>"
@@ -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: "<!i><i18n:item.table_lamp>"
@@ -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: "<!i><i18n:item.wooden_chair>"

View File

@@ -1,6 +1,6 @@
items:
default:topaz_ore:
material: paper
material: nether_brick
custom-model-data: 1010
data:
item-name: "<!i><i18n:item.topaz_ore>"
@@ -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: "<!i><i18n:item.deepslate_topaz_ore>"
@@ -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:

View File

@@ -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:

View File

@@ -1,6 +1,6 @@
items:
default:fairy_flower:
material: paper
material: nether_brick
custom-model-data: 4000
data:
item-name: "<!i><i18n:item.fairy_flower>"
@@ -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: "<!i><i18n:item.reed>"
@@ -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: "<!i><i18n:item.flame_cane>"
@@ -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: "<!i><i18n:item.ender_pearl_flower_seeds>"

View File

@@ -0,0 +1,5 @@
author: XiaoMoMi
version: 0.0.1
description: Remove Shulker Head for Some Versions
namespace: minecraft
enable: false

View File

@@ -59,6 +59,10 @@ command.resource.enable.failure.unknown: "<red>Unknown resource <arg:0></red>"
command.resource.disable.success: "<white>Disabled resource <arg:0>. Run <click:run_command:/ce reload all><u>/ce reload all</u></click> to apply changes</white>"
command.resource.disable.failure.unknown: "<red>Unknown resource <arg:0></red>"
command.resource.list: "<white>Enabled resources(<arg:0>): <green><arg:1></green><newline>Disabled resources(<arg:2>): <red><arg:3></red></white>"
command.upload.failure.not_supported: "<red>Current hosting method '<arg:0>' doesn't support uploading resource packs.</red>"
command.upload.on_progress: "<white>Started uploading progress. Check the console for more information.</white>"
command.send_resource_pack.success.single: "<white>Sent resource pack to <arg:0>.</white>"
command.send_resource_pack.success.multiple: "<white>Send resource packs to <arg:0> players.</white>"
warning.config.image.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated image '<arg:1>'.</yellow>"
warning.config.image.lack_height: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is missing the required 'height' argument.</yellow>"
warning.config.image.height_smaller_than_ascent: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' violates the bitmap image rule: 'height' should be no lower than 'ascent'.</yellow>"

View File

@@ -59,6 +59,10 @@ command.resource.enable.failure.unknown: "<red>未知资源 <arg:0></red>"
command.resource.disable.success: "<white>已禁用 <arg:0>. 执行 <click:run_command:/ce reload all><u>/ce reload all</u></click> 以应用更改</white>"
command.resource.disable.failure.unknown: "<red>未知资源 <arg:0></red>"
command.resource.list: "<white>启用的资源(<arg:0>): <green><arg:1></green><newline>禁用的资源(<arg:2>): <red><arg:3></red></white>"
command.upload.failure.not_supported: "<red>当前托管模式 '<arg:0>' 不支持上传资源包.</red>"
command.upload.on_progress: "<white>已开始上传进程. 检查控制台以获取详细信息.</white>"
command.send_resource_pack.success.single: "<white>发送资源包给 <arg:0></white>"
command.send_resource_pack.success.multiple: "<white>发送资源包给 <arg:0> 个玩家</white>"
warning.config.image.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 重复定义</yellow>"
warning.config.image.lack_height: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 缺少必要的 'height' 高度参数</yellow>"
warning.config.image.height_smaller_than_ascent: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 违反位图规则:'height' 高度值不应小于 'ascent' 基准线高度</yellow>"

View File

@@ -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);

View File

@@ -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<String> cachedNamespaces = manager.namespacesInUse();
String[] args = message.split(" ");
boolean modified = false;
for (int i = 1; i < args.length; i++) {
String[] parts = args[i].split(",");
List<String> 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<String> 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;
}
}
}

View File

@@ -96,19 +96,19 @@ public class BushBlockBehavior extends BukkitBlockBehavior {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments, false);
return new BushBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right());
}
}
public static Tuple<List<Object>, Set<Object>, Set<String>> readTagsAndState(Map<String, Object> arguments) {
public static Tuple<List<Object>, Set<Object>, Set<String>> readTagsAndState(Map<String, Object> arguments, boolean aboveOrBelow) {
List<Object> 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<Object> mcBlocks = new HashSet<>();
Set<String> 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()));

View File

@@ -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<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments, false);
Property<Integer> ageProperty = (Property<Integer>) block.getProperty("age");
if (ageProperty == null) {
throw new IllegalArgumentException("age property not set for crop");

View File

@@ -31,7 +31,7 @@ public class HangingBlockBehavior extends BushBlockBehavior {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments, true);
return new HangingBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right());
}
}

View File

@@ -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<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
Tuple<List<Object>, Set<Object>, Set<String>> 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)));
}

View File

@@ -200,7 +200,7 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments, false);
Property<Integer> ageProperty = (Property<Integer>) block.getProperty("age");
if (ageProperty == null) {
throw new IllegalArgumentException("age property not set for sugar cane");

View File

@@ -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);
}
}

View File

@@ -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<Object> 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<Object> 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<Object> getCachedValues() {

View File

@@ -143,9 +143,6 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
elements.add(furnitureElement);
}
// add colliders
List<Collider> colliders = new ArrayList<>();
// external model providers
Optional<ExternalModel> externalModel;
if (placementArguments.containsKey("model-engine")) {
@@ -162,7 +159,6 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager {
for (Map<String, Object> 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

View File

@@ -90,10 +90,12 @@ public class LoadedFurniture implements Furniture {
List<Object> packets = new ArrayList<>();
List<Object> minimizedPackets = new ArrayList<>();
List<Collider> 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();

View File

@@ -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);
}
}

View File

@@ -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<Object> 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<Object, Boolean> packets, Consumer<Collider> 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<Integer> entityIdSupplier) {
return new int[] {entityIdSupplier.get()};
}
public static class Factory implements HitBoxFactory {
@Override
public HitBox create(Map<String, Object> 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);
}
}
}

View File

@@ -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<Object, Boolean> packets) {
public void initPacketsAndColliders(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer<Object, Boolean> packets, Consumer<Collider> collider) {
// todo 乐魂
}

View File

@@ -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<Object, Boolean> packets) {
public void initPacketsAndColliders(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer<Object, Boolean> packets, Consumer<Collider> 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

View File

@@ -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<Object> cachedShulkerValues = new ArrayList<>();
private final List<Object> 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<Object> 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<Collider> 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<Object, Boolean> packets) {
public void initPacketsAndColliders(int[] entityIds, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer<Object, Boolean> packets, Consumer<Collider> 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<Object, Boolean> packets, Consumer<Collider> collider);
}
@Override
public int[] acquireEntityIds(Supplier<Integer> 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);
}
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<List<ResourcePackDownloadData>> 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<Object> 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;
});
}
}

View File

@@ -49,7 +49,9 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
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<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager();
manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true);

View File

@@ -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<CommandSender> {
@@ -33,7 +34,7 @@ public class DebugSetBlockCommand extends BukkitCommandFeature<CommandSender> {
.required("id", StringParser.stringComponent(StringParser.StringMode.GREEDY_FLAG_YIELDING).suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> 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 -> {

View File

@@ -35,7 +35,6 @@ public class ReloadCommand extends BukkitCommandFeature<CommandSender> {
}
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<CommandSender> {
}
} 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<CommandSender> {
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);

View File

@@ -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<CommandSender> {
public SendResourcePackCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.flag(FlagKeys.SILENT_FLAG)
.required("player", MultiplePlayerSelectorParser.multiplePlayerSelectorParser(true))
.handler(context -> {
MultiplePlayerSelector selector = context.get("player");
Collection<Player> 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";
}
}

View File

@@ -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<CommandSender> {
@@ -23,10 +19,6 @@ public class TestCommand extends BukkitCommandFeature<CommandSender> {
.senderType(Player.class)
.handler(context -> {
Player player = context.sender();
ItemStack itemStack = new ItemStack(Material.STONE);
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(itemStack);
wrapped.lore(null);
player.getInventory().addItem(wrapped.load());
});
}

View File

@@ -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<CommandSender> {
public UploadPackCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> 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";
}
}

View File

@@ -39,6 +39,7 @@ import java.util.function.BiConsumer;
public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener {
private static BukkitNetworkManager instance;
private static final Map<Class<?>, TriConsumer<NetWorkUser, NMSPacketEvent, Object>> NMS_PACKET_HANDLERS = new HashMap<>();
// only for game stage for the moment
private static final Map<Integer, BiConsumer<NetWorkUser, ByteBufPacketEvent>> BYTE_BUFFER_PACKET_HANDLERS = new HashMap<>();
private static void registerNMSPacketConsumer(final TriConsumer<NetWorkUser, NMSPacketEvent, Object> 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<String> handlerNames = channel.pipeline().names();

View File

@@ -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<Integer, Integer> map, int registrySize) {
mappings = new int[registrySize];
@@ -1156,6 +1160,28 @@ public class PacketConsumers {
}
}
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> 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> uuid = (Optional<UUID>) 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<NetWorkUser, NMSPacketEvent, Object> 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<NetWorkUser, NMSPacketEvent, Object> 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);
}
};
}

View File

@@ -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<UUID> resourcePackUUID = Collections.synchronizedSet(new HashSet<>());
// some references
private Reference<org.bukkit.entity.Player> playerRef;
private Reference<Object> 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();
}
}
}

View File

@@ -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)
);
}

View File

@@ -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()));
}
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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<String> suggestions = new HashSet<>();
private final Set<String> used = new HashSet<>();
private String input;
private int replaceCursor;
private Holder<CustomBlock> block;
private Collection<Property<?>> 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<String> fillSuggestions(@NotNull String data) {
return fillSuggestions(data, 0);
}
public static Set<String> 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 {

View File

@@ -58,7 +58,6 @@ public class CustomFurniture {
public record Placement(FurnitureElement[] elements,
HitBox[] hitBoxes,
Collider[] colliders,
RotationRule rotationRule,
AlignmentRule alignmentRule,
Optional<ExternalModel> externalModel) {

View File

@@ -21,5 +21,5 @@ public interface FurnitureElement {
Vector3f position();
void addSpawnPackets(int entityId, double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<Object> packets);
void initPackets(int entityId, double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<Object> packets);
}

View File

@@ -32,6 +32,7 @@ public class FurnitureSettings {
}
public static FurnitureSettings applyModifiers(FurnitureSettings settings, Map<String, Object> map) {
if (map == null) return settings;
for (Map.Entry<String, Object> entry : map.entrySet()) {
FurnitureSettings.Modifier.Factory factory = FurnitureSettings.Modifiers.FACTORIES.get(entry.getKey());
if (factory != null) {

View File

@@ -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<Object, Boolean> packets);
void initPacketsAndColliders(int[] entityId, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer<Object, Boolean> packets, Consumer<Collider> collider);
int[] acquireEntityIds(Supplier<Integer> entityIdSupplier);
Seat[] seats();
Vector3f position();
default Optional<Collider> optionalCollider() {
return Optional.empty();
}
}

View File

@@ -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<HitBoxFactory> holder = ((WritableRegistry<HitBoxFactory>) BuiltInRegistries.HITBOX_FACTORY)

View File

@@ -82,4 +82,6 @@ public abstract class Player extends Entity implements NetWorkUser {
public abstract void closeInventory();
public abstract void clearView();
public abstract void unloadCurrentResourcePack();
}

View File

@@ -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];
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<String, Pack> loadedPacks = new HashMap<>();
private final Map<String, ConfigSectionParser> sectionParsers = new HashMap<>();
private final TreeMap<ConfigSectionParser, List<CachedConfig>> cachedConfigs = new TreeMap<>();
protected BiConsumer<Path, Path> zipGenerator;
protected String packHash;
protected UUID packUUID;
protected ResourcePackHost resourcePackHost;
public AbstractPackManager(CraftEngine plugin, BiConsumer<Path, Path> 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<Map<?, ?>> 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<Pair<Path, List<Path>>> mergeFolder(Collection<Path> sourceFolders, Path targetFolder) throws IOException {
Map<Path, List<Path>> conflictChecker = new HashMap<>();
for (Path sourceFolder : sourceFolders) {

View File

@@ -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);
}

View File

@@ -1,7 +0,0 @@
package net.momirealms.craftengine.core.pack.host;
public enum HostMode {
SELF_HOST,
EXTERNAL_HOST,
NONE
}

View File

@@ -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);
}
}

View File

@@ -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<String, IpAccessRecord> ipAccessMap = new ConcurrentHashMap<>();
private int rateLimit = 1;
private long rateLimitInterval = 1000;
public interface ResourcePackHost {
public String url() {
return Config.hostProtocol() + "://" + ip + ":" + port + "/";
}
CompletableFuture<List<ResourcePackDownloadData>> 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<Void> 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();
}

View File

@@ -0,0 +1,8 @@
package net.momirealms.craftengine.core.pack.host;
import java.util.Map;
public interface ResourcePackHostFactory {
ResourcePackHost create(Map<String, Object> arguments);
}

View File

@@ -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<ResourcePackHostFactory> holder = ((WritableRegistry<ResourcePackHostFactory>) BuiltInRegistries.RESOURCE_PACK_HOST_FACTORY)
.registerForHolder(new ResourceKey<>(Registries.RESOURCE_PACK_HOST_FACTORY.location(), key));
holder.bindValue(factory);
}
public static ResourcePackHost fromMap(Map<String, Object> 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);
}
}

View File

@@ -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<String, Date> 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<String, String> cache = GsonHelper.get().fromJson(
new InputStreamReader(is),
new TypeToken<Map<String, String>>(){}.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<String, String> 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<List<ResourcePackDownloadData>> requestResourcePackDownloadLink(UUID player) {
CompletableFuture<List<ResourcePackDownloadData>> 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<Void> upload(Path resourcePackPath) {
if (this.disableUpload) {
this.cachedSha1 = "";
saveCacheToDisk();
return CompletableFuture.completedFuture(null);
}
CompletableFuture<Void> 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<String> 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<String> response, CompletableFuture<List<ResourcePackDownloadData>> 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<InputStream> 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<String, Object> 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);
}
}
}

View File

@@ -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<List<ResourcePackDownloadData>> 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<Void> upload(Path resourcePackPath) {
CompletableFuture<Void> 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<String> 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<String> 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<String> 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<String, Object> 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);
}
}
}

View File

@@ -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<List<ResourcePackDownloadData>> requestResourcePackDownloadLink(UUID player) {
return CompletableFuture.completedFuture(List.of(this.downloadData));
}
@Override
public CompletableFuture<Void> 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<String, Object> 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));
}
}
}

View File

@@ -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<String, String> cache = GsonHelper.get().fromJson(
new InputStreamReader(is),
new TypeToken<Map<String, String>>(){}.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<String, String> 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<List<ResourcePackDownloadData>> 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<Void> upload(Path resourcePackPath) {
CompletableFuture<Void> future = new CompletableFuture<>();
long totalStartTime = System.currentTimeMillis();
CraftEngine.instance().scheduler().executeAsync(() -> {
try {
Map<String, String> 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<AccountInfo> 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<String, String> calculateHashes(Path path) throws IOException, NoSuchAlgorithmException {
Map<String, String> 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<byte[]> 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<String> response,
CompletableFuture<Void> future,
String localSha1
) {
try {
if (response.statusCode() == 200) {
Map<String, Object> 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<String, Object> 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<String, Object> account_info;
private Map<String, Integer> account_limits;
private Map<String, Integer> 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;
}
}
}

View File

@@ -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<List<ResourcePackDownloadData>> requestResourcePackDownloadLink(UUID player) {
return CompletableFuture.completedFuture(List.of());
}
@Override
public CompletableFuture<Void> 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<String, Object> arguments) {
return INSTANCE;
}
}
}

View File

@@ -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<String, String, Date> 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<String, String> cache = GsonHelper.get().fromJson(
new InputStreamReader(is),
new TypeToken<Map<String, String>>(){}.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<String, String> 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<List<ResourcePackDownloadData>> requestResourcePackDownloadLink(UUID player) {
CompletableFuture<List<ResourcePackDownloadData>> 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<Void> upload(Path resourcePackPath) {
CompletableFuture<Void> 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<String> 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<String, Object> 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);
}
}
}

View File

@@ -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<List<ResourcePackDownloadData>> 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<Void> 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<String, Object> 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<String, Object> 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<String, Object> 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);
}
}
}

View File

@@ -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<List<ResourcePackDownloadData>> 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<Void> upload(Path resourcePackPath) {
CompletableFuture<Void> 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<String, Object> 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<String, Object> 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;
}
}
}

View File

@@ -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<String, Boolean> oneTimePackUrls = Caffeine.newBuilder()
.maximumSize(256)
.expireAfterAccess(1, TimeUnit.MINUTES)
.build();
private final Cache<String, IpAccessRecord> 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<String, String> 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<String, String> parseQuery(String query) {
Map<String, String> 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;
}
}
}

View File

@@ -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;

View File

@@ -130,6 +130,7 @@ public final class ObfD {
long 原始大小;
byte[] 编码路径;
int 压缩方法;
boolean 兄弟别搞;
}
private static class 文件条目注册表<E> extends ArrayList<E> {
@@ -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;
写入签名头(上下文, 压缩头验证器.中央目录标记);
}

View File

@@ -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<CraftEngine> 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

View File

@@ -41,6 +41,8 @@ public interface Plugin {
boolean isReloading();
boolean isInitializing();
DependencyManager dependencyManager();
<W> SchedulerAdapter<W> scheduler();

View File

@@ -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", "<yellow>To fully experience our server, please accept our custom resource pack.</yellow>"));
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", "<yellow>To fully experience our server, please accept our custom resource pack.</yellow>"));
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<ConditionalResolution> 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");

View File

@@ -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();
}
};
}

View File

@@ -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;

View File

@@ -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");
}

View File

@@ -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);
}

View File

@@ -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<ResolutionFactory> RESOLUTION_FACTORY = createRegistry(Registries.RESOLUTION_FACTORY);
public static final Registry<CustomSmithingTransformRecipe.ItemDataProcessor.Factory> SMITHING_RESULT_PROCESSOR_FACTORY = createRegistry(Registries.SMITHING_RESULT_PROCESSOR_FACTORY);
public static final Registry<HitBoxFactory> HITBOX_FACTORY = createRegistry(Registries.HITBOX_FACTORY);
public static final Registry<ResourcePackHostFactory> RESOURCE_PACK_HOST_FACTORY = createRegistry(Registries.RESOURCE_PACK_HOST_FACTORY);
private static <T> Registry<T> createRegistry(ResourceKey<? extends Registry<T>> key) {
return new MappedRegistry<>(key);

View File

@@ -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<Registry<ResolutionFactory>> RESOLUTION_FACTORY = new ResourceKey<>(ROOT_REGISTRY, Key.withDefaultNamespace("resolution_factory"));
public static final ResourceKey<Registry<CustomSmithingTransformRecipe.ItemDataProcessor.Factory>> SMITHING_RESULT_PROCESSOR_FACTORY = new ResourceKey<>(ROOT_REGISTRY, Key.withDefaultNamespace("smithing_result_processor_factory"));
public static final ResourceKey<Registry<HitBoxFactory>> HITBOX_FACTORY = new ResourceKey<>(ROOT_REGISTRY, Key.withDefaultNamespace("hitbox_factory"));
public static final ResourceKey<Registry<ResourcePackHostFactory>> RESOURCE_PACK_HOST_FACTORY = new ResourceKey<>(ROOT_REGISTRY, Key.withDefaultNamespace("resource_pack_host_factory"));
}

View File

@@ -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 <T extends CompletableFuture<?>> Collector<T, ImmutableList.Builder<T>, CompletableFuture<Void>> collector() {
return Collector.of(
ImmutableList.Builder::new,
ImmutableList.Builder::add,
(l, r) -> l.addAll(r.build()),
builder -> allOf(builder.build())
);
}
public static CompletableFuture<Void> allOf(Stream<? extends CompletableFuture<?>> futures) {
CompletableFuture<?>[] arr = futures.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(arr);
}
public static CompletableFuture<Void> allOf(Collection<? extends CompletableFuture<?>> futures) {
CompletableFuture<?>[] arr = futures.toArray(new CompletableFuture[0]);
return CompletableFuture.allOf(arr);
}
}

View File

@@ -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;

View File

@@ -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<String, Object> parseJsonToMap(String json) {
try {
return GsonHelper.get().fromJson(
json,
new TypeToken<Map<String, Object>>() {}.getType()
);
} catch (JsonSyntaxException e) {
throw new RuntimeException("Invalid JSON response: " + json, e);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<String, Object> 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;
}
}

View File

@@ -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

View File

@@ -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;