From 9a083244cef50af6fc82a9cb8866e06598ca6a91 Mon Sep 17 00:00:00 2001 From: Patbox <39821509+Patbox@users.noreply.github.com> Date: Thu, 7 Dec 2023 18:28:20 +0100 Subject: [PATCH] Update to 1.20.4, bug fixes, add port-reusing, address detecting autohost provider, make resource pack generation path configurable --- gradle.properties | 8 +- polymer-autohost/build.gradle | 1 + .../api/ResourcePackDataProvider.java | 11 +- .../pb4/polymer/autohost/impl/AutoHost.java | 59 ++++---- .../polymer/autohost/impl/AutoHostConfig.java | 9 +- .../polymer/autohost/impl/AutoHostTask.java | 61 ++++++++ .../autohost/impl/ClientConnectionExt.java | 12 ++ .../impl/netty/CustomHttpServerHandler.java | 136 ++++++++++++++++++ .../autohost/impl/netty/ProtocolSwitcher.java | 78 ++++++++++ .../impl/providers/AbstractProvider.java | 74 ++++++++++ .../impl/providers/NettyProvider.java | 28 ++++ ....java => StandaloneWebServerProvider.java} | 57 ++------ .../autohost/mixin/ClientConnectionMixin.java | 46 ++++++ ...erverConfigurationNetworkHandlerMixin.java | 28 +++- .../ServerHandshakeNetworkHandlerMixin.java | 23 +++ .../resources/polymer-autohost.mixins.json | 4 +- .../pb4/polymer/common/impl/CompatStatus.java | 2 + .../api/PolymerResourcePackUtils.java | 4 +- .../resourcepack/api/ResourcePackBuilder.java | 2 + .../impl/PolymerResourcePackImpl.java | 8 ++ .../impl/generation/DefaultRPBuilder.java | 35 ++++- .../virtualentity/api/VirtualEntityUtils.java | 13 +- .../api/attachment/ManualAttachment.java | 4 +- .../api/elements/DisplayElement.java | 8 ++ .../SetCameraEntityS2CPacketAccessor.java | 13 ++ .../polymer-virtual-entity.mixins.json | 1 + 26 files changed, 623 insertions(+), 102 deletions(-) create mode 100644 polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHostTask.java create mode 100644 polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/ClientConnectionExt.java create mode 100644 polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/netty/CustomHttpServerHandler.java create mode 100644 polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/netty/ProtocolSwitcher.java create mode 100644 polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/AbstractProvider.java create mode 100644 polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/NettyProvider.java rename polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/{WebServerProvider.java => StandaloneWebServerProvider.java} (67%) create mode 100644 polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ClientConnectionMixin.java create mode 100644 polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ServerHandshakeNetworkHandlerMixin.java create mode 100644 polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/mixin/SetCameraEntityS2CPacketAccessor.java diff --git a/gradle.properties b/gradle.properties index 7709067c..45549362 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,16 +3,16 @@ org.gradle.jvmargs=-Xmx2G # Fabric Properties # check these on https://fabricmc.net/use -minecraft_version=1.20.3-rc1 -yarn_mappings=1.20.3-rc1+build.1 +minecraft_version=1.20.4 +yarn_mappings=1.20.4+build.1 loader_version=0.15.0 #Fabric api -fabric_version=0.90.11+1.20.3 +fabric_version=0.91.1+1.20.4 maven_group = eu.pb4 -mod_version = 0.7.0 +mod_version = 0.7.1 minecraft_version_supported = ">=1.20.3-" diff --git a/polymer-autohost/build.gradle b/polymer-autohost/build.gradle index 5c530fc7..86a449ef 100644 --- a/polymer-autohost/build.gradle +++ b/polymer-autohost/build.gradle @@ -38,6 +38,7 @@ dependencies { compileOnly (project(path: ':polymer-virtual-entity', configuration: 'namedElements')) localRuntime (project(path: ':polymer-virtual-entity', configuration: 'namedElements')) //modLocalRuntime("xyz.nucleoid:server-translations-api:2.0.0-beta.2+1.19.4-pre2") + api include('io.netty:netty-codec-http:4.1.82.Final') project(":polymer-core").afterEvaluate { testmodImplementation project(":polymer-core").sourceSets.testmod.output diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/api/ResourcePackDataProvider.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/api/ResourcePackDataProvider.java index 1aae620e..49cf6cf5 100644 --- a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/api/ResourcePackDataProvider.java +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/api/ResourcePackDataProvider.java @@ -4,11 +4,13 @@ import com.google.gson.JsonObject; import eu.pb4.polymer.autohost.impl.AutoHost; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; +import net.minecraft.network.ClientConnection; import net.minecraft.server.MinecraftServer; import net.minecraft.util.Identifier; import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.List; import java.util.UUID; import java.util.function.Supplier; @@ -26,7 +28,14 @@ static ResourcePackDataProvider getActive() { static void register(Identifier identifier, Supplier providerCreator) { AutoHost.TYPES.put(identifier, providerCreator); }; - Collection getProperties(); + default Collection getProperties(ClientConnection connection) { + return getProperties(); + }; + + @Deprecated + default Collection getProperties() { + return List.of(); + }; static MinecraftServer.ServerResourcePackProperties createProperties(@Nullable UUID uuid, String address, @Nullable String hash) { return new MinecraftServer.ServerResourcePackProperties(uuid, address, hash, AutoHost.config.require || PolymerResourcePackUtils.isRequired(), AutoHost.message); diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHost.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHost.java index 9a84b7aa..57df789d 100644 --- a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHost.java +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHost.java @@ -1,10 +1,12 @@ package eu.pb4.polymer.autohost.impl; import eu.pb4.polymer.autohost.api.ResourcePackDataProvider; +import eu.pb4.polymer.autohost.impl.providers.NettyProvider; import eu.pb4.polymer.autohost.impl.providers.EmptyProvider; -import eu.pb4.polymer.autohost.impl.providers.WebServerProvider; +import eu.pb4.polymer.autohost.impl.providers.StandaloneWebServerProvider; import eu.pb4.polymer.common.impl.CommonImpl; import eu.pb4.polymer.common.impl.CommonImplUtils; +import eu.pb4.polymer.common.impl.CommonNetworkHandlerExt; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import net.fabricmc.api.ModInitializer; import net.minecraft.network.packet.s2c.common.ResourcePackSendS2CPacket; @@ -22,17 +24,32 @@ public class AutoHost implements ModInitializer { public static final Map> TYPES = new HashMap<>(); - public static AutoHostConfig config; - public static Text message; - public static Text disconnectMessage; + public static AutoHostConfig config = new AutoHostConfig(); + public static Text message = Text.empty(); + public static Text disconnectMessage = Text.empty(); public static ResourcePackDataProvider provider = EmptyProvider.INSTANCE; public static void init(MinecraftServer server) { + var config = CommonImpl.loadConfig("auto-host", AutoHostConfig.class); + AutoHost.config = config; + if (!config.enabled) { return; } + try { + AutoHost.message = Text.Serialization.fromJsonTree(AutoHost.config.message); + } catch (Exception e) { + AutoHost.message = null; + } + + try { + AutoHost.disconnectMessage = Text.Serialization.fromJsonTree(AutoHost.config.disconnectMessage); + } catch (Exception e) { + AutoHost.disconnectMessage = Text.literal("This server requires resource pack enabled to play!"); + } + var type = TYPES.get(Identifier.tryParse(config.type)); @@ -51,8 +68,6 @@ public static void init(MinecraftServer server) { CommonImpl.saveConfig("auto-host", config); - - //EarlyPlayNetworkHandler.register(ResourcePackNetworkHandler::create); provider.serverStarted(server); } @@ -78,32 +93,19 @@ public static void generateAndCall(MinecraftServer server, Consumer messag @Override public void onInitialize() { - ResourcePackDataProvider.register(new Identifier("polymer", "http_server"), WebServerProvider::new); + ResourcePackDataProvider.register(new Identifier("polymer", "automatic"), NettyProvider::new); + ResourcePackDataProvider.register(new Identifier("polymer", "auto"), NettyProvider::new); + ResourcePackDataProvider.register(new Identifier("polymer", "netty"), NettyProvider::new); + ResourcePackDataProvider.register(new Identifier("polymer", "same_port"), NettyProvider::new); + ResourcePackDataProvider.register(new Identifier("polymer", "http_server"), StandaloneWebServerProvider::new); + ResourcePackDataProvider.register(new Identifier("polymer", "standalone"), StandaloneWebServerProvider::new); ResourcePackDataProvider.register(new Identifier("polymer", "empty"), EmptyProvider::new); - var config = CommonImpl.loadConfig("auto-host", AutoHostConfig.class); - AutoHost.config = config; - - if (!config.enabled) { - return; - } - - try { - AutoHost.message = Text.Serialization.fromJsonTree(AutoHost.config.message); - } catch (Exception e) { - AutoHost.message = null; - } - - try { - AutoHost.disconnectMessage = Text.Serialization.fromJsonTree(AutoHost.config.disconnectMessage); - } catch (Exception e) { - AutoHost.disconnectMessage = Text.literal("This server requires resource pack enabled to play!"); - } - CommonImplUtils.registerDevCommands((c) -> { c.then(literal("reload_resourcepack").executes(context -> { if (provider.isReady()) { - for (var x : provider.getProperties()) { + for (var x : provider.getProperties(((CommonNetworkHandlerExt) context.getSource().getPlayerOrThrow().networkHandler).polymerCommon$getConnection() + )) { context.getSource().getPlayerOrThrow().networkHandler.sendPacket(new ResourcePackSendS2CPacket(x.id(), x.url(), x.hash(), AutoHost.config.require || PolymerResourcePackUtils.isRequired(), AutoHost.message)); } } @@ -111,7 +113,8 @@ public void onInitialize() { })); c.then(literal("rebuild_reload_rp").executes(context -> { generateAndCall(context.getSource().getServer(), context.getSource()::sendMessage, () -> { - for (var x : provider.getProperties()) { + for (var x : provider.getProperties(((CommonNetworkHandlerExt) context.getSource().getPlayer().networkHandler).polymerCommon$getConnection() + )) { context.getSource().getPlayer().networkHandler.sendPacket(new ResourcePackSendS2CPacket(x.id(), x.url(), x.hash(), AutoHost.config.require || PolymerResourcePackUtils.isRequired(), AutoHost.message)); } }); diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHostConfig.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHostConfig.java index 04966898..0c58d518 100644 --- a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHostConfig.java +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHostConfig.java @@ -1,23 +1,24 @@ package eu.pb4.polymer.autohost.impl; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.annotations.SerializedName; -import eu.pb4.polymer.autohost.impl.providers.WebServerProvider; import eu.pb4.polymer.common.impl.CommonImpl; +import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; public class AutoHostConfig { public String _c1 = "Enables Polymer's ResourcePack Auto Hosting"; @SerializedName("enabled") - public boolean enabled = CommonImpl.DEV_ENV; + public boolean enabled = CommonImpl.DEV_ENV || PolymerResourcePackUtils.isRequired(); public String _c2 = "Marks resource pack as required"; @SerializedName("required") public boolean require = false; public String _c3 = "Type of resource pack provider. Default: 'polymer:http_server'"; - public String type = "polymer:http_server"; + public String type = "polymer:automatic"; public String _c4 = "Configuration of type, see provider's source for more details"; @SerializedName("settings") - public JsonElement providerSettings = new WebServerProvider.Config().toJson(); + public JsonElement providerSettings = new JsonObject(); public String _c5 = "Message sent to clients before pack is loaded"; public JsonElement message = new JsonPrimitive("This server uses resource pack to enhance gameplay with custom textures and models. It might be unplayable without them."); diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHostTask.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHostTask.java new file mode 100644 index 00000000..90aa1978 --- /dev/null +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/AutoHostTask.java @@ -0,0 +1,61 @@ +package eu.pb4.polymer.autohost.impl; + +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.c2s.common.ResourcePackStatusC2SPacket; +import net.minecraft.network.packet.s2c.common.ResourcePackSendS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.server.network.ServerPlayerConfigurationTask; +import net.minecraft.text.Text; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; + +public class AutoHostTask implements ServerPlayerConfigurationTask { + public static final Key KEY = new Key("polymer:autohost/send_packs"); + private final Collection packs; + + private final Set requiredPacks = new HashSet<>(); + private final Set waitingFor = new HashSet<>(); + + public AutoHostTask(Collection properties) { + this.packs = properties; + for (var pack : packs) { + if (pack.isRequired()) { + requiredPacks.add(pack.id()); + } + waitingFor.add(pack.id()); + } + } + + @Override + public void sendPacket(Consumer> sender) { + for (var pack : packs) { + sender.accept(new ResourcePackSendS2CPacket(pack.id(), pack.url(), pack.hash(), pack.isRequired(), pack.prompt())); + } + } + + @Override + public Key getKey() { + return KEY; + } + + public boolean onStatus(ServerConfigurationNetworkHandler handler, UUID id, ResourcePackStatusC2SPacket.Status status) { + switch (status) { + case DECLINED, FAILED_RELOAD, FAILED_DOWNLOAD, INVALID_URL -> { + if (this.requiredPacks.contains(id)) { + handler.disconnect(Text.translatable("multiplayer.requiredTexturePrompt.disconnect")); + } + } + } + + if (status.hasFinished()) { + this.waitingFor.remove(id); + } + + return this.waitingFor.isEmpty(); + } +} diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/ClientConnectionExt.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/ClientConnectionExt.java new file mode 100644 index 00000000..17003c59 --- /dev/null +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/ClientConnectionExt.java @@ -0,0 +1,12 @@ +package eu.pb4.polymer.autohost.impl; + +public interface ClientConnectionExt { + void polymerAutoHost$setAddress(String address, int port); + + String polymerAutoHost$getAddress(); + int polymerAutoHost$getPort(); + + default String polymerAutoHost$getFullAddress() { + return polymerAutoHost$getAddress() + ":" + polymerAutoHost$getPort(); + } +} diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/netty/CustomHttpServerHandler.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/netty/CustomHttpServerHandler.java new file mode 100644 index 00000000..ad12b9cd --- /dev/null +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/netty/CustomHttpServerHandler.java @@ -0,0 +1,136 @@ +package eu.pb4.polymer.autohost.impl.netty; + +import eu.pb4.polymer.autohost.api.ResourcePackDataProvider; +import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.handler.codec.http.*; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.stream.ChunkedFile; +import io.netty.util.CharsetUtil; +import io.netty.util.internal.SystemPropertyUtil; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Pattern; + +import static io.netty.handler.codec.http.HttpMethod.*; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.*; +public class CustomHttpServerHandler extends SimpleChannelInboundHandler { + private FullHttpRequest request; + + @Override + public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { + + this.request = request; + if (!request.decoderResult().isSuccess()) { + sendError(ctx, BAD_REQUEST); + return; + } + + if (!GET.equals(request.method())) { + sendError(ctx, METHOD_NOT_ALLOWED); + return; + } + + final boolean keepAlive = HttpUtil.isKeepAlive(request); + + File file = PolymerResourcePackUtils.getMainPath().toFile(); + + if (!file.isFile() || !ResourcePackDataProvider.getActive().isReady()) { + sendError(ctx, FORBIDDEN); + return; + } + + RandomAccessFile raf; + try { + raf = new RandomAccessFile(file, "r"); + } catch (FileNotFoundException ignore) { + sendError(ctx, NOT_FOUND); + return; + } + long fileLength = raf.length(); + + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); + HttpUtil.setContentLength(response, fileLength); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/zip"); + + if (!keepAlive) { + response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); + } else if (request.protocolVersion().equals(HTTP_1_0)) { + response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); + } + + // Write the initial line and the header. + ctx.write(response); + + // Write the content. + ChannelFuture sendFileFuture; + ChannelFuture lastContentFuture; + if (ctx.pipeline().get(SslHandler.class) == null) { + sendFileFuture = + ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise()); + // Write the end marker. + lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); + } else { + sendFileFuture = + ctx.writeAndFlush(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)), + ctx.newProgressivePromise()); + // HttpChunkedInput will write the end marker (LastHttpContent) for us. + lastContentFuture = sendFileFuture; + } + + // Decide whether to close the connection or not. + if (!keepAlive) { + // Close the connection when the whole content is written out. + lastContentFuture.addListener(ChannelFutureListener.CLOSE); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + if (ctx.channel().isActive()) { + sendError(ctx, INTERNAL_SERVER_ERROR); + } + } + + private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { + FullHttpResponse response = new DefaultFullHttpResponse( + HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8)); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8"); + + sendAndCleanupConnection(ctx, response); + } + + /** + * If Keep-Alive is disabled, attaches "Connection: close" header to the response + * and closes the connection after the response being sent. + */ + private void sendAndCleanupConnection(ChannelHandlerContext ctx, FullHttpResponse response) { + final FullHttpRequest request = this.request; + final boolean keepAlive = HttpUtil.isKeepAlive(request); + HttpUtil.setContentLength(response, response.content().readableBytes()); + if (!keepAlive) { + // We're going to close the connection as soon as the response is sent, + // so we should also make it clear for the client. + response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); + } else if (request.protocolVersion().equals(HTTP_1_0)) { + response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); + } + + ChannelFuture flushPromise = ctx.writeAndFlush(response); + + if (!keepAlive) { + // Close the connection as soon as the response is sent. + flushPromise.addListener(ChannelFutureListener.CLOSE); + } + } +} \ No newline at end of file diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/netty/ProtocolSwitcher.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/netty/ProtocolSwitcher.java new file mode 100644 index 00000000..b9eedbb8 --- /dev/null +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/netty/ProtocolSwitcher.java @@ -0,0 +1,78 @@ +package eu.pb4.polymer.autohost.impl.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.stream.ChunkedWriteHandler; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class ProtocolSwitcher extends ChannelInboundHandlerAdapter { + public static final String ID = "polymer:autohost/protocol_switcher"; + @Override + public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object msg) throws Exception { + var buf = (ByteBuf) msg; + var i = buf.readerIndex(); + + if (buf.readableBytes() > 2 && isHttp(buf.getByte(i), buf.getByte(i + 1))) { + var val = new StringBuilder(); + var store = false; + var space = 0; + int y = 0; + while (y < buf.readableBytes()) { + var character = (char) buf.getByte(i + y); + + if (character == ' ') { + if (space == 0) { + store = true; + } else { + break; + } + space++; + } else if (store) { + val.append(character); + } + y++; + } + + if (val.toString().startsWith("/eu.pb4.polymer.autohost/")) { + var p = ctx.channel().pipeline(); + while (p.last() != null) { + p.removeLast(); + } + + p.addLast(new HttpServerCodec()); + p.addLast(new HttpObjectAggregator(65536)); + p.addLast(new ChunkedWriteHandler()); + p.addLast(new CustomHttpServerHandler()); + ctx.pipeline().fireChannelRead(msg); + return; + } + } + ctx.channel().pipeline().remove(ID); + buf.readerIndex(i); + ctx.fireChannelRead(msg); + } + + private static boolean isHttp(int magic1, int magic2) { + return magic1 == 'G' && magic2 == 'E' || // GET + magic1 == 'P' && magic2 == 'O' || // POST + magic1 == 'P' && magic2 == 'U' || // PUT + magic1 == 'H' && magic2 == 'E' || // HEAD + magic1 == 'O' && magic2 == 'P' || // OPTIONS + magic1 == 'P' && magic2 == 'A' || // PATCH + magic1 == 'D' && magic2 == 'E' || // DELETE + magic1 == 'T' && magic2 == 'R' || // TRACE + magic1 == 'C' && magic2 == 'O'; // CONNECT + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + ctx.close(); + } +} diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/AbstractProvider.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/AbstractProvider.java new file mode 100644 index 00000000..5dd169ea --- /dev/null +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/AbstractProvider.java @@ -0,0 +1,74 @@ +package eu.pb4.polymer.autohost.impl.providers; + +import com.google.common.base.Strings; +import com.google.common.hash.Hashing; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import com.sun.net.httpserver.HttpExchange; +import eu.pb4.polymer.autohost.api.ResourcePackDataProvider; +import eu.pb4.polymer.autohost.impl.AutoHost; +import eu.pb4.polymer.common.impl.CommonImpl; +import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; +import net.minecraft.network.ClientConnection; +import net.minecraft.server.MinecraftServer; +import org.apache.http.HttpStatus; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.util.Collection; +import java.util.List; + +public abstract class AbstractProvider implements ResourcePackDataProvider { + public long size = 0; + public String hash = ""; + public long lastUpdate = 0; + public boolean enabled; + public boolean isPackReady = false; + + public void serverStarted(MinecraftServer minecraftServer) { + this.enabled = true; + + this.isPackReady = true; + updateHash(); + + PolymerResourcePackUtils.RESOURCE_PACK_CREATION_EVENT.register((x) -> { + isPackReady = false; + }); + + PolymerResourcePackUtils.RESOURCE_PACK_FINISHED_EVENT.register(() -> { + isPackReady = true; + updateHash(); + }); + + AutoHost.generateAndCall(minecraftServer, minecraftServer::sendMessage, () -> { + }); + } + + protected boolean updateHash() { + try { + hash = com.google.common.io.Files.asByteSource(PolymerResourcePackUtils.getMainPath().toFile()).hash(Hashing.sha1()).toString(); + size = Files.size(PolymerResourcePackUtils.getMainPath()); + lastUpdate = Files.getLastModifiedTime(PolymerResourcePackUtils.getMainPath()).toMillis(); + return true; + } catch (Exception e) { + hash = ""; + size = 0; + return false; + } + + } + + @Override + public Collection getProperties(ClientConnection connection) { + return List.of(ResourcePackDataProvider.createProperties(PolymerResourcePackUtils.getMainUuid(), this.getAddress(connection), this.hash)); + } + + protected abstract String getAddress(ClientConnection connection); + + @Override + public boolean isReady() { + return this.isPackReady; + } +} \ No newline at end of file diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/NettyProvider.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/NettyProvider.java new file mode 100644 index 00000000..e65fcf0c --- /dev/null +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/NettyProvider.java @@ -0,0 +1,28 @@ +package eu.pb4.polymer.autohost.impl.providers; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import eu.pb4.polymer.autohost.impl.ClientConnectionExt; +import net.minecraft.network.ClientConnection; +import net.minecraft.server.MinecraftServer; + +public class NettyProvider extends AbstractProvider { + public static final NettyProvider INSTANCE = new NettyProvider(); + @Override + public JsonElement saveSettings() { + return new JsonObject(); + } + + @Override + public void loadSettings(JsonElement settings) { + + } + + @Override + public void serverStopped(MinecraftServer server) {} + + @Override + protected String getAddress(ClientConnection connection) { + return "http://" + ((ClientConnectionExt) connection).polymerAutoHost$getFullAddress() + "/eu.pb4.polymer.autohost/" + this.hash + ".zip"; + } +} diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/WebServerProvider.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/StandaloneWebServerProvider.java similarity index 67% rename from polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/WebServerProvider.java rename to polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/StandaloneWebServerProvider.java index d37d4f4b..856a4aa3 100644 --- a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/WebServerProvider.java +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/impl/providers/StandaloneWebServerProvider.java @@ -1,17 +1,13 @@ package eu.pb4.polymer.autohost.impl.providers; import com.google.common.base.Strings; -import com.google.common.hash.Hashing; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.gson.annotations.SerializedName; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; -import eu.pb4.polymer.autohost.api.ResourcePackDataProvider; -import eu.pb4.polymer.autohost.impl.AutoHost; import eu.pb4.polymer.common.impl.CommonImpl; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; +import net.minecraft.network.ClientConnection; import net.minecraft.server.MinecraftServer; import org.apache.http.HttpStatus; import org.jetbrains.annotations.Nullable; @@ -19,21 +15,13 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.nio.file.Files; -import java.util.Collection; -import java.util.List; -import java.util.UUID; import java.util.concurrent.Executors; -public class WebServerProvider implements ResourcePackDataProvider { +public class StandaloneWebServerProvider extends AbstractProvider { private Config config; private HttpServer server; - public long size = 0; - public String hash = ""; - public long lastUpdate = 0; public String baseAddress = ""; public String fullAddress = ""; - public boolean enabled; - public boolean isPackReady = false; @Nullable public void serverStarted(MinecraftServer minecraftServer) { @@ -45,25 +33,12 @@ public void serverStarted(MinecraftServer minecraftServer) { server.setExecutor(Executors.newFixedThreadPool(2)); server.start(); - this.enabled = true; this.baseAddress = config.externalAddress; - if (!this.baseAddress.endsWith("/")) { this.baseAddress += "/"; } - this.isPackReady = true; - updateHash(); - - PolymerResourcePackUtils.RESOURCE_PACK_CREATION_EVENT.register((x) -> { - isPackReady = false; - }); - - PolymerResourcePackUtils.RESOURCE_PACK_FINISHED_EVENT.register(() -> { - isPackReady = true; - updateHash(); - }); - AutoHost.generateAndCall(minecraftServer, minecraftServer::sendMessage, () -> {}); + super.serverStarted(minecraftServer); } catch (IOException e) { e.printStackTrace(); } @@ -74,17 +49,17 @@ public void serverStopped(MinecraftServer minecraftServer) { server.stop(0); } - private void updateHash() { - try { - hash = com.google.common.io.Files.asByteSource(PolymerResourcePackUtils.getMainPath().toFile()).hash(Hashing.sha1()).toString(); - size = Files.size(PolymerResourcePackUtils.getMainPath()); - lastUpdate = Files.getLastModifiedTime(PolymerResourcePackUtils.getMainPath()).toMillis(); + protected boolean updateHash() { + if (super.updateHash()) { this.fullAddress = this.baseAddress + this.hash + ".zip"; - } catch (Exception e) { - hash = ""; - size = 0; + return true; } + return false; + } + @Override + protected String getAddress(ClientConnection connection) { + return this.fullAddress; } private static InetSocketAddress createBindAddress(MinecraftServer server, Config config) { @@ -120,16 +95,6 @@ public void handle(HttpExchange exchange) throws IOException { } } - @Override - public Collection getProperties() { - return List.of(ResourcePackDataProvider.createProperties(PolymerResourcePackUtils.getMainUuid(), this.fullAddress, this.hash)); - } - - @Override - public boolean isReady() { - return this.isPackReady; - } - @Override public JsonElement saveSettings() { return this.config.toJson(); diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ClientConnectionMixin.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ClientConnectionMixin.java new file mode 100644 index 00000000..e183a39b --- /dev/null +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ClientConnectionMixin.java @@ -0,0 +1,46 @@ +package eu.pb4.polymer.autohost.mixin; + +import eu.pb4.polymer.autohost.api.ResourcePackDataProvider; +import eu.pb4.polymer.autohost.impl.ClientConnectionExt; +import eu.pb4.polymer.autohost.impl.netty.ProtocolSwitcher; +import eu.pb4.polymer.autohost.impl.providers.NettyProvider; +import io.netty.channel.ChannelPipeline; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.NetworkSide; +import net.minecraft.network.handler.PacketSizeLogger; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = ClientConnection.class, priority = 10000) +public class ClientConnectionMixin implements ClientConnectionExt { + @Unique + private String connAddress = ""; + @Unique + private int connPort = -1; + + @Override + public void polymerAutoHost$setAddress(String address, int port) { + this.connAddress = address; + this.connPort = port; + } + + @Override + public String polymerAutoHost$getAddress() { + return this.connAddress; + } + + @Override + public int polymerAutoHost$getPort() { + return this.connPort; + } + + @Inject(method = "addHandlers", at = @At("TAIL")) + private static void addHttpHandlers(ChannelPipeline pipeline, NetworkSide side, PacketSizeLogger packetSizeLogger, CallbackInfo ci) { + if (side == NetworkSide.SERVERBOUND && ResourcePackDataProvider.getActive() instanceof NettyProvider) { + pipeline.addFirst(ProtocolSwitcher.ID, new ProtocolSwitcher()); + } + } +} diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ServerConfigurationNetworkHandlerMixin.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ServerConfigurationNetworkHandlerMixin.java index d31b7313..9ef60590 100644 --- a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ServerConfigurationNetworkHandlerMixin.java +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ServerConfigurationNetworkHandlerMixin.java @@ -1,11 +1,18 @@ package eu.pb4.polymer.autohost.mixin; +import com.llamalad7.mixinextras.injector.WrapWithCondition; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; import eu.pb4.polymer.autohost.impl.AutoHost; +import eu.pb4.polymer.autohost.impl.AutoHostTask; import eu.pb4.polymer.common.api.PolymerCommonUtils; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.c2s.common.ResourcePackStatusC2SPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.*; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -23,12 +30,27 @@ public ServerConfigurationNetworkHandlerMixin(MinecraftServer server, ClientConn @Shadow @Final private Queue tasks; + @Shadow @Nullable private ServerPlayerConfigurationTask currentTask; + + @Shadow protected abstract void onTaskFinished(ServerPlayerConfigurationTask.Key key); + @Inject(method = "queueSendResourcePackTask", at = @At("TAIL")) private void polymerAutoHost$addTask(CallbackInfo ci) { if (AutoHost.config.enabled) { - for (var x : AutoHost.provider.getProperties()) { - this.tasks.add(new SendResourcePackTask(x)); - } + this.tasks.add(new AutoHostTask(AutoHost.provider.getProperties(this.connection))); } } + + @Inject(method = "onResourcePackStatus", at = @At("TAIL")) + private void onStatus(ResourcePackStatusC2SPacket packet, CallbackInfo ci) { + if (this.currentTask instanceof AutoHostTask task && task.onStatus((ServerConfigurationNetworkHandler) (Object) this, packet.id(), packet.status())) { + this.onTaskFinished(AutoHostTask.KEY); + } + } + + @WrapWithCondition(method = "onResourcePackStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerConfigurationNetworkHandler;onTaskFinished(Lnet/minecraft/server/network/ServerPlayerConfigurationTask$Key;)V")) + private boolean checkType(ServerConfigurationNetworkHandler instance, ServerPlayerConfigurationTask.Key key, @Local ResourcePackStatusC2SPacket packet) { + return key != SendResourcePackTask.KEY + || (this.server.getResourcePackProperties().isPresent() && this.server.getResourcePackProperties().get().id().equals(packet.id())); + } } diff --git a/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ServerHandshakeNetworkHandlerMixin.java b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ServerHandshakeNetworkHandlerMixin.java new file mode 100644 index 00000000..c964ba48 --- /dev/null +++ b/polymer-autohost/src/main/java/eu/pb4/polymer/autohost/mixin/ServerHandshakeNetworkHandlerMixin.java @@ -0,0 +1,23 @@ +package eu.pb4.polymer.autohost.mixin; + +import eu.pb4.polymer.autohost.impl.ClientConnectionExt; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import net.minecraft.server.network.ServerConfigurationNetworkHandler; +import net.minecraft.server.network.ServerHandshakeNetworkHandler; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerHandshakeNetworkHandler.class) +public class ServerHandshakeNetworkHandlerMixin { + @Shadow @Final private ClientConnection connection; + + @Inject(method = "onHandshake", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerLoginNetworkHandler;(Lnet/minecraft/server/MinecraftServer;Lnet/minecraft/network/ClientConnection;)V")) + private void storeConnectionData(HandshakeC2SPacket packet, CallbackInfo ci) { + ((ClientConnectionExt) this.connection).polymerAutoHost$setAddress(packet.address(), packet.port()); + } +} diff --git a/polymer-autohost/src/main/resources/polymer-autohost.mixins.json b/polymer-autohost/src/main/resources/polymer-autohost.mixins.json index 1dc67b51..8f4c72d5 100644 --- a/polymer-autohost/src/main/resources/polymer-autohost.mixins.json +++ b/polymer-autohost/src/main/resources/polymer-autohost.mixins.json @@ -4,8 +4,10 @@ "package": "eu.pb4.polymer.autohost.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ + "ClientConnectionMixin", "MinecraftServerMixin", - "ServerConfigurationNetworkHandlerMixin" + "ServerConfigurationNetworkHandlerMixin", + "ServerHandshakeNetworkHandlerMixin" ], "injectors": { "defaultRequire": 1 diff --git a/polymer-common/src/main/java/eu/pb4/polymer/common/impl/CompatStatus.java b/polymer-common/src/main/java/eu/pb4/polymer/common/impl/CompatStatus.java index eebd7077..a10a9860 100644 --- a/polymer-common/src/main/java/eu/pb4/polymer/common/impl/CompatStatus.java +++ b/polymer-common/src/main/java/eu/pb4/polymer/common/impl/CompatStatus.java @@ -49,6 +49,8 @@ public final class CompatStatus { public static final boolean PROXY_MODS = FABRIC_PROXY || FABRIC_PROXY_LITE || QFORWARD || FAPROXY; + public static final boolean E4MC = LOADER.isModLoaded("e4mc_minecraft"); + public static final boolean FLOODGATE = LOADER.isModLoaded("floodgate"); public static final boolean IRIS = LOADER.isModLoaded("iris"); diff --git a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/PolymerResourcePackUtils.java b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/PolymerResourcePackUtils.java index e7ab2396..6b89b053 100644 --- a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/PolymerResourcePackUtils.java +++ b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/PolymerResourcePackUtils.java @@ -24,8 +24,6 @@ * Global utilities allowing creation of single, polymer mod compatible resource pack */ public final class PolymerResourcePackUtils { - private static final Path DEFAULT_PATH = FabricLoader.getInstance().getGameDir().resolve("polymer-resourcepack.zip").toAbsolutePath().normalize(); - private PolymerResourcePackUtils() { } @@ -120,7 +118,7 @@ public static boolean hasMainPack(@Nullable ServerPlayerEntity player) { } public static Path getMainPath() { - return DEFAULT_PATH; + return PolymerResourcePackImpl.DEFAULT_PATH; } public static UUID getMainUuid() { diff --git a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/ResourcePackBuilder.java b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/ResourcePackBuilder.java index 0c167246..0e95fb3d 100644 --- a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/ResourcePackBuilder.java +++ b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/ResourcePackBuilder.java @@ -4,6 +4,7 @@ import org.jetbrains.annotations.Nullable; import java.nio.file.Path; +import java.util.function.BiFunction; @ApiStatus.NonExtendable public interface ResourcePackBuilder { @@ -29,4 +30,5 @@ default boolean copyFromPath(Path path, boolean override) { byte[] getDataOrSource(String path); boolean addAssetsSource(String modId); + void addWriteConverter(BiFunction converter); } diff --git a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/impl/PolymerResourcePackImpl.java b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/impl/PolymerResourcePackImpl.java index 11e24d9b..a766ffd6 100644 --- a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/impl/PolymerResourcePackImpl.java +++ b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/impl/PolymerResourcePackImpl.java @@ -3,7 +3,9 @@ import com.google.gson.annotations.SerializedName; import eu.pb4.polymer.common.impl.CommonImpl; import eu.pb4.polymer.common.impl.CompatStatus; +import net.fabricmc.loader.api.FabricLoader; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -16,11 +18,14 @@ public class PolymerResourcePackImpl { public static final List INCLUDE_MOD_IDS; public static final List INCLUDE_ZIPS; public static final UUID MAIN_UUID; + public static final Path DEFAULT_PATH; static { var config = CommonImpl.loadConfig("resource-pack", Config.class); + DEFAULT_PATH = FabricLoader.getInstance().getGameDir().resolve(config.resourcePackPath); + MAIN_UUID = config.mainUuid; FORCE_REQUIRE = config.markResourcePackAsRequiredByDefault || CompatStatus.POLYMC; @@ -60,5 +65,8 @@ public static class Config { public String _c6 = "Included resource packs from zips!"; @SerializedName("include_zips") public List includeZips = List.of("world/resources.zip"); + public String _c7 = "Path used for creation of default resourcepack!"; + @SerializedName("resource_pack_location") + public String resourcePackPath = "polymer/resource_pack.zip"; } } diff --git a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/impl/generation/DefaultRPBuilder.java b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/impl/generation/DefaultRPBuilder.java index b0b69bcd..bd845295 100644 --- a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/impl/generation/DefaultRPBuilder.java +++ b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/impl/generation/DefaultRPBuilder.java @@ -30,7 +30,9 @@ import java.util.List; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -49,7 +51,8 @@ public class DefaultRPBuilder implements InternalRPBuilder { private final List modsList = new ArrayList<>(); private final Map> customModelData = new HashMap<>(); private final Map atlasDefinitions = new HashMap<>(); - private List rootPaths = new ArrayList<>(); + private final List rootPaths = new ArrayList<>(); + private final List> converters = new ArrayList<>(); private boolean hasVanilla; public DefaultRPBuilder(Path outputPath) { @@ -287,6 +290,11 @@ public boolean addAssetsSource(String modId) { return false; } + @Override + public void addWriteConverter(BiFunction converter) { + this.converters.add(converter); + } + @Nullable private byte[] getSourceData(String path) { try { @@ -650,12 +658,26 @@ public CompletableFuture buildResourcePack() { var sorted = new ArrayList<>(this.fileMap.entrySet()); sorted.sort(Map.Entry.comparingByKey()); for (var entry : sorted) { - var zipEntry = new ZipEntry(entry.getKey()); + var outByte = entry.getValue(); + var path = entry.getKey(); + if (outByte != null) { + for (var conv : converters) { + outByte = conv.apply(path, outByte); + if (outByte == null) { + break; + } + } + + if (outByte == null) { + continue; + } + } + + var zipEntry = new ZipEntry(path); zipEntry.setTime(0); outputStream.putNextEntry(zipEntry); - var value = entry.getValue(); - if (value != null) { - outputStream.write(entry.getValue()); + if (outByte != null) { + outputStream.write(outByte); } outputStream.closeEntry(); } @@ -665,8 +687,7 @@ public CompletableFuture buildResourcePack() { return bool; } catch (Exception e) { - CommonImpl.LOGGER.error("Something went wrong while creating resource pack!"); - e.printStackTrace(); + CommonImpl.LOGGER.error("Something went wrong while creating resource pack!", e); return false; } }); diff --git a/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/VirtualEntityUtils.java b/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/VirtualEntityUtils.java index dbe73c5b..c92855e3 100644 --- a/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/VirtualEntityUtils.java +++ b/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/VirtualEntityUtils.java @@ -5,6 +5,7 @@ import eu.pb4.polymer.virtualentity.impl.EntityExt; import eu.pb4.polymer.virtualentity.impl.compat.ImmersivePortalsUtils; import eu.pb4.polymer.virtualentity.mixin.EntityPassengersSetS2CPacketAccessor; +import eu.pb4.polymer.virtualentity.mixin.SetCameraEntityS2CPacketAccessor; import eu.pb4.polymer.virtualentity.mixin.accessors.EntityAccessor; import eu.pb4.polymer.virtualentity.mixin.accessors.EntityPositionS2CPacketAccessor; import eu.pb4.polymer.virtualentity.mixin.accessors.PlaySoundFromEntityS2CPacketAccessor; @@ -12,10 +13,7 @@ import net.minecraft.entity.Entity; import net.minecraft.network.listener.ClientPlayPacketListener; import net.minecraft.network.packet.Packet; -import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; -import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket; -import net.minecraft.network.packet.s2c.play.EntityS2CPacket; -import net.minecraft.network.packet.s2c.play.PlaySoundFromEntityS2CPacket; +import net.minecraft.network.packet.s2c.play.*; import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; @@ -57,6 +55,13 @@ public static void removeVirtualPassenger(Entity entity, int... passengerId) { ((EntityExt) entity).polymerVE$markVirtualRiddenDirty(); } + public static SetCameraEntityS2CPacket createSetCameraEntityPacket(int entityId) { + var packet = PolymerCommonUtils.createUnsafe(SetCameraEntityS2CPacket.class); + var ac = (SetCameraEntityS2CPacketAccessor) packet; + ac.setEntityId(entityId); + return packet; + } + public static PlaySoundFromEntityS2CPacket createPlaySoundFromEntityPacket(int entityId, RegistryEntry sound, SoundCategory category, float volume, float pitch, long seed) { var packet = PolymerCommonUtils.createUnsafe(PlaySoundFromEntityS2CPacket.class); var ac = (PlaySoundFromEntityS2CPacketAccessor) packet; diff --git a/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/attachment/ManualAttachment.java b/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/attachment/ManualAttachment.java index cd2d4884..e9912632 100644 --- a/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/attachment/ManualAttachment.java +++ b/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/attachment/ManualAttachment.java @@ -11,7 +11,9 @@ public record ManualAttachment(ElementHolder holder, ServerWorld world, Supplier posSupplier) implements HolderAttachment { @Override public void destroy() { - this.holder.destroy(); + if (this.holder.getAttachment() == this) { + this.holder.setAttachment(null); + } } @Override diff --git a/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/elements/DisplayElement.java b/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/elements/DisplayElement.java index d12ff836..5b87ba8d 100644 --- a/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/elements/DisplayElement.java +++ b/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/api/elements/DisplayElement.java @@ -80,6 +80,14 @@ public void setInterpolationDuration(int interpolationDuration) { this.dataTracker.set(DisplayTrackedData.INTERPOLATION_DURATION, interpolationDuration); } + public int getTeleportDuration() { + return this.dataTracker.get(DisplayTrackedData.TELEPORTATION_DURATION); + } + + public void setTeleportDuration(int interpolationDuration) { + this.dataTracker.set(DisplayTrackedData.TELEPORTATION_DURATION, interpolationDuration); + } + public int getStartInterpolation() { return this.dataTracker.get(DisplayTrackedData.START_INTERPOLATION); } diff --git a/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/mixin/SetCameraEntityS2CPacketAccessor.java b/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/mixin/SetCameraEntityS2CPacketAccessor.java new file mode 100644 index 00000000..c8dcdc22 --- /dev/null +++ b/polymer-virtual-entity/src/main/java/eu/pb4/polymer/virtualentity/mixin/SetCameraEntityS2CPacketAccessor.java @@ -0,0 +1,13 @@ +package eu.pb4.polymer.virtualentity.mixin; + +import net.minecraft.network.packet.s2c.play.SetCameraEntityS2CPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(SetCameraEntityS2CPacket.class) +public interface SetCameraEntityS2CPacketAccessor { + @Mutable + @Accessor + void setEntityId(int id); +} diff --git a/polymer-virtual-entity/src/main/resources/polymer-virtual-entity.mixins.json b/polymer-virtual-entity/src/main/resources/polymer-virtual-entity.mixins.json index f6e7e9d1..782504d9 100644 --- a/polymer-virtual-entity/src/main/resources/polymer-virtual-entity.mixins.json +++ b/polymer-virtual-entity/src/main/resources/polymer-virtual-entity.mixins.json @@ -14,6 +14,7 @@ "PlayerInteractEntityC2SPacketAccessor", "ServerPlayerEntityMixin", "ServerPlayNetworkHandlerMixin", + "SetCameraEntityS2CPacketAccessor", "SlimeEntityAccessor", "accessors.BlockDisplayEntityAccessor", "accessors.DisplayEntityAccessor",