From 2522884da3a2da8e5752e7eb6646df208cc1e2bb Mon Sep 17 00:00:00 2001 From: DeadlyMC <43000902+DeadlyMC@users.noreply.github.com> Date: Thu, 28 May 2020 00:53:52 +0530 Subject: [PATCH 1/7] Client-server syncing (#295) * Sync carpet settings * Sync tick rate --- src/main/java/carpet/CarpetServer.java | 7 ++ .../carpet/helpers/ClientNetworkHandler.java | 59 +++++++++++++++++ .../carpet/helpers/ServerNetworkHandler.java | 66 +++++++++++++++++++ src/main/java/carpet/helpers/TickSpeed.java | 1 + .../mixins/ClientPlayNetworkHandlerMixin.java | 44 +++++++++++++ src/main/resources/carpet.mixins.json | 3 +- 6 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 src/main/java/carpet/helpers/ClientNetworkHandler.java create mode 100644 src/main/java/carpet/helpers/ServerNetworkHandler.java create mode 100644 src/main/java/carpet/mixins/ClientPlayNetworkHandlerMixin.java diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java index 554a4ca1..f86cfa31 100644 --- a/src/main/java/carpet/CarpetServer.java +++ b/src/main/java/carpet/CarpetServer.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Random; import carpet.commands.*; +import carpet.helpers.ServerNetworkHandler; import carpet.helpers.TickSpeed; import carpet.logging.LoggerRegistry; import carpet.script.CarpetScriptServer; @@ -17,6 +18,7 @@ import net.fabricmc.loader.api.FabricLoader; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; public class CarpetServer // static for now - easier to handle all around the code, its one anyways { @@ -26,6 +28,7 @@ public class CarpetServer // static for now - easier to handle all around the co public static CarpetScriptServer scriptServer; public static SettingsManager settingsManager; public static final List extensions = new ArrayList<>(); + public static final Identifier CARPET_CHANNEL = new Identifier("carpet:info"); // Separate from onServerLoaded, because a server can be loaded multiple times in singleplayer public static void manageExtension(CarpetExtension extension) @@ -45,6 +48,9 @@ public class CarpetServer // static for now - easier to handle all around the co settingsManager = new SettingsManager(CarpetSettings.carpetVersion, "carpet", "Carpet Mod"); settingsManager.parseSettingsClass(CarpetSettings.class); extensions.forEach(CarpetExtension::onGameStarted); + CarpetServer.settingsManager.addRuleObserver((source, parsedRule, s) -> { + ServerNetworkHandler.sendUpdateToClient(parsedRule.name, parsedRule.getAsString(), source); + }); } public static void onServerLoaded(MinecraftServer server) @@ -104,6 +110,7 @@ public class CarpetServer // static for now - easier to handle all around the co { LoggerRegistry.playerConnected(player); extensions.forEach(e -> e.onPlayerLoggedIn(player)); + ServerNetworkHandler.onPlayerJoin(player); } public static void onPlayerLoggedOut(ServerPlayerEntity player) diff --git a/src/main/java/carpet/helpers/ClientNetworkHandler.java b/src/main/java/carpet/helpers/ClientNetworkHandler.java new file mode 100644 index 00000000..5e15e6ba --- /dev/null +++ b/src/main/java/carpet/helpers/ClientNetworkHandler.java @@ -0,0 +1,59 @@ +package carpet.helpers; + +import carpet.CarpetServer; +import carpet.CarpetSettings; +import carpet.settings.ParsedRule; +import net.minecraft.client.MinecraftClient; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.util.Identifier; +import net.minecraft.util.PacketByteBuf; + +public class ClientNetworkHandler +{ + public static void handleData(Identifier channel, PacketByteBuf data) + { + if (CarpetServer.CARPET_CHANNEL.equals(channel) && data != null) + { + int id = data.readVarInt(); + + if (id == 1) + setOnJoinInfo(data); + else if (id == 2) + updateRule(data); + else if (id == 3) + updateTickRateClient(data); + } + } + + private static void setOnJoinInfo(PacketByteBuf data) + { + CompoundTag compound = data.readCompoundTag(); + if (compound == null) return; + + TickSpeed.tickrate(compound.getFloat("Tickrate")); + + ListTag rulesList = compound.getList("rules", 10); + for (Tag tag : rulesList) + { + CompoundTag ruleNBT = (CompoundTag) tag; + String rule = ruleNBT.getString("rule"); + String value = ruleNBT.getString("value"); + CarpetServer.settingsManager.getRule(rule).set(null, value); + } + } + + private static void updateRule(PacketByteBuf data) + { + String rule = data.readString(); + String newValue = data.readString(); + CarpetServer.settingsManager.getRule(rule).set(null, newValue); + } + + private static void updateTickRateClient(PacketByteBuf data) + { + float rate = data.readFloat(); + TickSpeed.tickrate(rate); + } +} diff --git a/src/main/java/carpet/helpers/ServerNetworkHandler.java b/src/main/java/carpet/helpers/ServerNetworkHandler.java new file mode 100644 index 00000000..77b393d3 --- /dev/null +++ b/src/main/java/carpet/helpers/ServerNetworkHandler.java @@ -0,0 +1,66 @@ +package carpet.helpers; + +import carpet.CarpetServer; +import carpet.CarpetSettings; +import carpet.settings.ParsedRule; +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.PacketByteBuf; + +public class ServerNetworkHandler +{ + public static void onPlayerJoin(ServerPlayerEntity playerEntity) + { + CompoundTag data = new CompoundTag(); + + data.putFloat("Tickrate", TickSpeed.tickrate); + + ListTag rulesList = new ListTag(); + for (ParsedRule rule : CarpetServer.settingsManager.getRules()) + { + CompoundTag ruleNBT = new CompoundTag(); + + ruleNBT.putString("rule" ,rule.name); + ruleNBT.putString("value", rule.getAsString()); + ruleNBT.putString("default", rule.defaultAsString); + rulesList.add(ruleNBT); + } + data.put("rules", rulesList); + PacketByteBuf packetBuf = new PacketByteBuf(Unpooled.buffer()); + packetBuf.writeVarInt(1); + packetBuf.writeCompoundTag(data); + playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetServer.CARPET_CHANNEL, packetBuf)); + } + + public static void sendUpdateToClient(String rule, String newValue, ServerCommandSource source) + { + PacketByteBuf data = new PacketByteBuf(Unpooled.buffer()); + + data.writeVarInt(2); + data.writeString(rule); + data.writeString(newValue); + for (ServerPlayerEntity player : source.getMinecraftServer().getPlayerManager().getPlayerList()) + { + player.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetServer.CARPET_CHANNEL, data)); + } + } + + public static void sendTickRateToPlayers(float rate) + { + PacketByteBuf data = new PacketByteBuf(Unpooled.buffer()); + data.writeVarInt(3); + data.writeFloat(rate); + if (CarpetServer.minecraft_server != null) + { + for (ServerPlayerEntity player : CarpetServer.minecraft_server.getPlayerManager().getPlayerList()) + { + player.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetServer.CARPET_CHANNEL, data)); + } + } + } +} diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java index 33d735b7..1c1c4d2b 100644 --- a/src/main/java/carpet/helpers/TickSpeed.java +++ b/src/main/java/carpet/helpers/TickSpeed.java @@ -221,6 +221,7 @@ public class TickSpeed } } } + ServerNetworkHandler.sendTickRateToPlayers(tickrate); } public static BiConsumer addTickrateListener(String modId, BiConsumer tickrateListener) diff --git a/src/main/java/carpet/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/carpet/mixins/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 00000000..0e8fafcf --- /dev/null +++ b/src/main/java/carpet/mixins/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,44 @@ +package carpet.mixins; + +import carpet.CarpetServer; +import carpet.helpers.ClientNetworkHandler; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; +import net.minecraft.util.Identifier; +import net.minecraft.util.PacketByteBuf; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(ClientPlayNetworkHandler.class) +public abstract class ClientPlayNetworkHandlerMixin +{ + @Inject(method = "onCustomPayload", at = @At(value = "CONSTANT", args = "stringValue=Unknown custom packed identifier: {}"), cancellable = true, locals = LocalCapture.CAPTURE_FAILSOFT, require = 0) + private void onCustomPayloadNotFound(CustomPayloadS2CPacket packet, CallbackInfo ci, Identifier id, PacketByteBuf buf) + { + if (CarpetServer.CARPET_CHANNEL.equals(id)) + { + if (buf.refCnt() > 0) + { + buf.release(); + } + + ci.cancel(); + } + } + + @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) + private void onOnCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci) + { + Identifier channel = packet.getChannel(); + PacketByteBuf buf = packet.getData(); + ClientNetworkHandler.handleData(channel, buf); + ci.cancel(); + } +} diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index c1356941..041dbaaa 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -177,7 +177,8 @@ "ClientPlayNetworkHandler_smoothClientAnimationsMixin", "ClientPlayerEntity_clientCommandMixin", "WorldRenderer_fogOffMixin", - "WorldRenderer_creativeNoClipMixin" + "WorldRenderer_creativeNoClipMixin", + "ClientPlayNetworkHandlerMixin" ], "server": [ From e540f9e2383f9d4268bd840ad147d833b7fe1e88 Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 28 May 2020 00:06:28 -0300 Subject: [PATCH 2/7] carpet client synchronizations and protocols --- src/main/java/carpet/CarpetServer.java | 24 ++-- src/main/java/carpet/CarpetSettings.java | 5 +- .../java/carpet/commands/TickCommand.java | 2 +- .../CustomPayloadC2SPacketInterface.java | 10 ++ .../carpet/helpers/ClientNetworkHandler.java | 59 --------- .../carpet/helpers/ServerNetworkHandler.java | 66 ---------- src/main/java/carpet/helpers/TickSpeed.java | 7 +- .../mixins/ClientPlayNetworkHandlerMixin.java | 51 ++++---- ...ClientPlayerEntity_clientCommandMixin.java | 7 +- .../mixins/CustomPayloadC2SPacketMixin.java | 27 ++++ .../carpet/mixins/MinecraftClientMixin.java | 8 ++ .../mixins/ServerPlayNetworkHandlerMixin.java | 31 +++++ .../java/carpet/network/CarpetClient.java | 52 ++++++++ .../carpet/network/ClientNetworkHandler.java | 93 +++++++++++++ .../carpet/network/ServerNetworkHandler.java | 123 ++++++++++++++++++ .../java/carpet/settings/SettingsManager.java | 6 +- src/main/java/carpet/settings/Validator.java | 2 +- src/main/java/carpet/utils/Messenger.java | 3 +- src/main/resources/carpet.mixins.json | 3 + 19 files changed, 406 insertions(+), 173 deletions(-) create mode 100644 src/main/java/carpet/fakes/CustomPayloadC2SPacketInterface.java delete mode 100644 src/main/java/carpet/helpers/ClientNetworkHandler.java delete mode 100644 src/main/java/carpet/helpers/ServerNetworkHandler.java create mode 100644 src/main/java/carpet/mixins/CustomPayloadC2SPacketMixin.java create mode 100644 src/main/java/carpet/mixins/ServerPlayNetworkHandlerMixin.java create mode 100644 src/main/java/carpet/network/CarpetClient.java create mode 100644 src/main/java/carpet/network/ClientNetworkHandler.java create mode 100644 src/main/java/carpet/network/ServerNetworkHandler.java diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java index f86cfa31..7cc7f021 100644 --- a/src/main/java/carpet/CarpetServer.java +++ b/src/main/java/carpet/CarpetServer.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.Random; import carpet.commands.*; -import carpet.helpers.ServerNetworkHandler; +import carpet.network.ServerNetworkHandler; import carpet.helpers.TickSpeed; import carpet.logging.LoggerRegistry; import carpet.script.CarpetScriptServer; @@ -18,7 +18,6 @@ import net.fabricmc.loader.api.FabricLoader; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.Identifier; public class CarpetServer // static for now - easier to handle all around the code, its one anyways { @@ -28,7 +27,6 @@ public class CarpetServer // static for now - easier to handle all around the co public static CarpetScriptServer scriptServer; public static SettingsManager settingsManager; public static final List extensions = new ArrayList<>(); - public static final Identifier CARPET_CHANNEL = new Identifier("carpet:info"); // Separate from onServerLoaded, because a server can be loaded multiple times in singleplayer public static void manageExtension(CarpetExtension extension) @@ -48,9 +46,6 @@ public class CarpetServer // static for now - easier to handle all around the co settingsManager = new SettingsManager(CarpetSettings.carpetVersion, "carpet", "Carpet Mod"); settingsManager.parseSettingsClass(CarpetSettings.class); extensions.forEach(CarpetExtension::onGameStarted); - CarpetServer.settingsManager.addRuleObserver((source, parsedRule, s) -> { - ServerNetworkHandler.sendUpdateToClient(parsedRule.name, parsedRule.getAsString(), source); - }); } public static void onServerLoaded(MinecraftServer server) @@ -66,6 +61,7 @@ public class CarpetServer // static for now - easier to handle all around the co scriptServer.loadAllWorldScripts(); MobAI.resetTrackers(); LoggerRegistry.initLoggers(); + //TickSpeed.reset(); } public static void tick(MinecraftServer server) @@ -108,31 +104,41 @@ public class CarpetServer // static for now - easier to handle all around the co public static void onPlayerLoggedIn(ServerPlayerEntity player) { + ServerNetworkHandler.onPlayerJoin(player); LoggerRegistry.playerConnected(player); extensions.forEach(e -> e.onPlayerLoggedIn(player)); - ServerNetworkHandler.onPlayerJoin(player); + } public static void onPlayerLoggedOut(ServerPlayerEntity player) { + ServerNetworkHandler.onPlayerLoggedOut(player); LoggerRegistry.playerDisconnected(player); extensions.forEach(e -> e.onPlayerLoggedOut(player)); } public static void onServerClosed(MinecraftServer server) { + ServerNetworkHandler.close(); currentCommandDispatcher = null; if (scriptServer != null) scriptServer.onClose(); - settingsManager.detachServer(); + LoggerRegistry.stopLoggers(); - TickSpeed.reset(); extensions.forEach(e -> e.onServerClosed(server)); minecraft_server = null; + disconnect(); } public static void registerExtensionLoggers() { extensions.forEach(CarpetExtension::registerLoggers); } + + public static void disconnect() + { + // this for whatever reason gets called multiple times even when joining; + TickSpeed.reset(); + settingsManager.detachServer(); + } } diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index 4913bb1f..a198b438 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -44,7 +44,7 @@ public class CarpetSettings private static class LanguageValidator extends Validator { @Override public String validate(ServerCommandSource source, ParsedRule currentRule, String newValue, String string) { - if (currentRule.get().equals(newValue)) + if (currentRule.get().equals(newValue) || source == null) { return newValue; } @@ -487,7 +487,7 @@ public class CarpetSettings { @Override public Integer validate(ServerCommandSource source, ParsedRule currentRule, Integer newValue, String string) { - if (currentRule.get().equals(newValue)) + if (currentRule.get().equals(newValue) || source == null) { return newValue; } @@ -538,6 +538,7 @@ public class CarpetSettings } } @Override public Integer validate(ServerCommandSource source, ParsedRule currentRule, Integer newValue, String string) { + if (source == null) return newValue; if (newValue < 0 || newValue > 32) { Messenger.m(source, "r spawn chunk size has to be between 0 and 32"); diff --git a/src/main/java/carpet/commands/TickCommand.java b/src/main/java/carpet/commands/TickCommand.java index cc7dc38c..0b559f38 100644 --- a/src/main/java/carpet/commands/TickCommand.java +++ b/src/main/java/carpet/commands/TickCommand.java @@ -67,7 +67,7 @@ public class TickCommand private static int setTps(ServerCommandSource source, float tps) { - TickSpeed.tickrate(tps); + TickSpeed.tickrate(tps, true); queryTps(source); return (int)tps; } diff --git a/src/main/java/carpet/fakes/CustomPayloadC2SPacketInterface.java b/src/main/java/carpet/fakes/CustomPayloadC2SPacketInterface.java new file mode 100644 index 00000000..baa636e7 --- /dev/null +++ b/src/main/java/carpet/fakes/CustomPayloadC2SPacketInterface.java @@ -0,0 +1,10 @@ +package carpet.fakes; + +import net.minecraft.util.Identifier; +import net.minecraft.util.PacketByteBuf; + +public interface CustomPayloadC2SPacketInterface +{ + Identifier getPacketChannel(); + PacketByteBuf getPacketData(); +} diff --git a/src/main/java/carpet/helpers/ClientNetworkHandler.java b/src/main/java/carpet/helpers/ClientNetworkHandler.java deleted file mode 100644 index 5e15e6ba..00000000 --- a/src/main/java/carpet/helpers/ClientNetworkHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -package carpet.helpers; - -import carpet.CarpetServer; -import carpet.CarpetSettings; -import carpet.settings.ParsedRule; -import net.minecraft.client.MinecraftClient; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.Tag; -import net.minecraft.util.Identifier; -import net.minecraft.util.PacketByteBuf; - -public class ClientNetworkHandler -{ - public static void handleData(Identifier channel, PacketByteBuf data) - { - if (CarpetServer.CARPET_CHANNEL.equals(channel) && data != null) - { - int id = data.readVarInt(); - - if (id == 1) - setOnJoinInfo(data); - else if (id == 2) - updateRule(data); - else if (id == 3) - updateTickRateClient(data); - } - } - - private static void setOnJoinInfo(PacketByteBuf data) - { - CompoundTag compound = data.readCompoundTag(); - if (compound == null) return; - - TickSpeed.tickrate(compound.getFloat("Tickrate")); - - ListTag rulesList = compound.getList("rules", 10); - for (Tag tag : rulesList) - { - CompoundTag ruleNBT = (CompoundTag) tag; - String rule = ruleNBT.getString("rule"); - String value = ruleNBT.getString("value"); - CarpetServer.settingsManager.getRule(rule).set(null, value); - } - } - - private static void updateRule(PacketByteBuf data) - { - String rule = data.readString(); - String newValue = data.readString(); - CarpetServer.settingsManager.getRule(rule).set(null, newValue); - } - - private static void updateTickRateClient(PacketByteBuf data) - { - float rate = data.readFloat(); - TickSpeed.tickrate(rate); - } -} diff --git a/src/main/java/carpet/helpers/ServerNetworkHandler.java b/src/main/java/carpet/helpers/ServerNetworkHandler.java deleted file mode 100644 index 77b393d3..00000000 --- a/src/main/java/carpet/helpers/ServerNetworkHandler.java +++ /dev/null @@ -1,66 +0,0 @@ -package carpet.helpers; - -import carpet.CarpetServer; -import carpet.CarpetSettings; -import carpet.settings.ParsedRule; -import io.netty.buffer.Unpooled; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.PacketByteBuf; - -public class ServerNetworkHandler -{ - public static void onPlayerJoin(ServerPlayerEntity playerEntity) - { - CompoundTag data = new CompoundTag(); - - data.putFloat("Tickrate", TickSpeed.tickrate); - - ListTag rulesList = new ListTag(); - for (ParsedRule rule : CarpetServer.settingsManager.getRules()) - { - CompoundTag ruleNBT = new CompoundTag(); - - ruleNBT.putString("rule" ,rule.name); - ruleNBT.putString("value", rule.getAsString()); - ruleNBT.putString("default", rule.defaultAsString); - rulesList.add(ruleNBT); - } - data.put("rules", rulesList); - PacketByteBuf packetBuf = new PacketByteBuf(Unpooled.buffer()); - packetBuf.writeVarInt(1); - packetBuf.writeCompoundTag(data); - playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetServer.CARPET_CHANNEL, packetBuf)); - } - - public static void sendUpdateToClient(String rule, String newValue, ServerCommandSource source) - { - PacketByteBuf data = new PacketByteBuf(Unpooled.buffer()); - - data.writeVarInt(2); - data.writeString(rule); - data.writeString(newValue); - for (ServerPlayerEntity player : source.getMinecraftServer().getPlayerManager().getPlayerList()) - { - player.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetServer.CARPET_CHANNEL, data)); - } - } - - public static void sendTickRateToPlayers(float rate) - { - PacketByteBuf data = new PacketByteBuf(Unpooled.buffer()); - data.writeVarInt(3); - data.writeFloat(rate); - if (CarpetServer.minecraft_server != null) - { - for (ServerPlayerEntity player : CarpetServer.minecraft_server.getPlayerManager().getPlayerList()) - { - player.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetServer.CARPET_CHANNEL, data)); - } - } - } -} diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java index 1c1c4d2b..091c7c73 100644 --- a/src/main/java/carpet/helpers/TickSpeed.java +++ b/src/main/java/carpet/helpers/TickSpeed.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.function.BiConsumer; import carpet.CarpetServer; +import carpet.network.ServerNetworkHandler; import carpet.utils.Messenger; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.MinecraftServer; @@ -177,7 +178,7 @@ public class TickSpeed } } - public static void tickrate(float rate) + public static void tickrate(float rate, boolean update) { tickrate = rate; long mspt = (long)(1000.0 / tickrate); @@ -189,7 +190,7 @@ public class TickSpeed TickSpeed.mspt = (float)mspt; - notifyTickrateListeners("carpet"); + if (update) notifyTickrateListeners("carpet"); } private static void tickrateChanged(String modId, float rate) @@ -221,7 +222,7 @@ public class TickSpeed } } } - ServerNetworkHandler.sendTickRateToPlayers(tickrate); + ServerNetworkHandler.updateTickSpeedToConnectedPlayers(); } public static BiConsumer addTickrateListener(String modId, BiConsumer tickrateListener) diff --git a/src/main/java/carpet/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/carpet/mixins/ClientPlayNetworkHandlerMixin.java index 0e8fafcf..362828ea 100644 --- a/src/main/java/carpet/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/carpet/mixins/ClientPlayNetworkHandlerMixin.java @@ -1,44 +1,43 @@ package carpet.mixins; -import carpet.CarpetServer; -import carpet.helpers.ClientNetworkHandler; +import carpet.network.CarpetClient; +import carpet.network.ClientNetworkHandler; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.Tag; import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; -import net.minecraft.util.Identifier; -import net.minecraft.util.PacketByteBuf; +import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket; +import net.minecraft.text.Text; 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; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @Mixin(ClientPlayNetworkHandler.class) public abstract class ClientPlayNetworkHandlerMixin { - @Inject(method = "onCustomPayload", at = @At(value = "CONSTANT", args = "stringValue=Unknown custom packed identifier: {}"), cancellable = true, locals = LocalCapture.CAPTURE_FAILSOFT, require = 0) - private void onCustomPayloadNotFound(CustomPayloadS2CPacket packet, CallbackInfo ci, Identifier id, PacketByteBuf buf) - { - if (CarpetServer.CARPET_CHANNEL.equals(id)) - { - if (buf.refCnt() > 0) - { - buf.release(); - } - - ci.cancel(); - } - } - + @Shadow private MinecraftClient client; + @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) private void onOnCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci) { - Identifier channel = packet.getChannel(); - PacketByteBuf buf = packet.getData(); - ClientNetworkHandler.handleData(channel, buf); - ci.cancel(); + if (CarpetClient.CARPET_CHANNEL.equals(packet.getChannel())) + { + ClientNetworkHandler.handleData(packet.getData(), client.player); + ci.cancel(); + } } + + @Inject(method = "onGameJoin", at = @At("RETURN")) + private void onGameJoined(GameJoinS2CPacket packet, CallbackInfo info) + { + CarpetClient.gameJoined(client.player); + } + + @Inject(method = "onDisconnected", at = @At("HEAD")) + private void onCMDisconnected(Text reason, CallbackInfo ci) + { + CarpetClient.disconnect(); + } + } diff --git a/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java b/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java index a0347524..16a41708 100644 --- a/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java +++ b/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java @@ -1,6 +1,7 @@ package carpet.mixins; import carpet.CarpetServer; +import carpet.network.CarpetClient; import net.minecraft.client.network.ClientPlayerEntity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -13,8 +14,10 @@ public class ClientPlayerEntity_clientCommandMixin @Inject(method = "sendChatMessage", at = @At("HEAD")) private void inspectMessage(String string, CallbackInfo ci) { - ClientPlayerEntity playerSource = (ClientPlayerEntity)(Object) this; - if (CarpetServer.minecraft_server == null) + if (CarpetServer.minecraft_server == null && !CarpetClient.isCarpet()) + { + ClientPlayerEntity playerSource = (ClientPlayerEntity)(Object) this; CarpetServer.settingsManager.inspectClientsideCommand(playerSource.getCommandSource(), string); + } } } diff --git a/src/main/java/carpet/mixins/CustomPayloadC2SPacketMixin.java b/src/main/java/carpet/mixins/CustomPayloadC2SPacketMixin.java new file mode 100644 index 00000000..0465c72e --- /dev/null +++ b/src/main/java/carpet/mixins/CustomPayloadC2SPacketMixin.java @@ -0,0 +1,27 @@ +package carpet.mixins; + +import carpet.fakes.CustomPayloadC2SPacketInterface; +import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; +import net.minecraft.util.Identifier; +import net.minecraft.util.PacketByteBuf; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(CustomPayloadC2SPacket.class) +public class CustomPayloadC2SPacketMixin implements CustomPayloadC2SPacketInterface +{ + @Shadow + private Identifier channel; + @Shadow + private PacketByteBuf data; + + @Override + public Identifier getPacketChannel() { + return channel; + } + + @Override + public PacketByteBuf getPacketData() { + return new PacketByteBuf(this.data.copy()); + } +} diff --git a/src/main/java/carpet/mixins/MinecraftClientMixin.java b/src/main/java/carpet/mixins/MinecraftClientMixin.java index 2598239a..c2c9be06 100644 --- a/src/main/java/carpet/mixins/MinecraftClientMixin.java +++ b/src/main/java/carpet/mixins/MinecraftClientMixin.java @@ -1,7 +1,9 @@ package carpet.mixins; import carpet.CarpetServer; +import carpet.network.CarpetClient; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -18,4 +20,10 @@ public class MinecraftClientMixin //CM start game hook CarpetServer.onGameStarted(); } + + @Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;)V", at = @At("HEAD")) + private void onCloseGame(Screen screen, CallbackInfo ci) + { + CarpetClient.disconnect(); + } } diff --git a/src/main/java/carpet/mixins/ServerPlayNetworkHandlerMixin.java b/src/main/java/carpet/mixins/ServerPlayNetworkHandlerMixin.java new file mode 100644 index 00000000..bec53d32 --- /dev/null +++ b/src/main/java/carpet/mixins/ServerPlayNetworkHandlerMixin.java @@ -0,0 +1,31 @@ +package carpet.mixins; + +import carpet.fakes.CustomPayloadC2SPacketInterface; +import carpet.network.CarpetClient; +import carpet.network.ServerNetworkHandler; +import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +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(ServerPlayNetworkHandler.class) +public class ServerPlayNetworkHandlerMixin +{ + @Shadow public ServerPlayerEntity player; + + @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) + private void onCustomCarpetPayload(CustomPayloadC2SPacket packet, CallbackInfo ci) + { + Identifier channel = ((CustomPayloadC2SPacketInterface) packet).getPacketChannel(); + if (CarpetClient.CARPET_CHANNEL.equals(channel)) + { + ServerNetworkHandler.handleData(((CustomPayloadC2SPacketInterface) packet).getPacketData(), player); + ci.cancel(); + } + } +} diff --git a/src/main/java/carpet/network/CarpetClient.java b/src/main/java/carpet/network/CarpetClient.java new file mode 100644 index 00000000..0846a9d0 --- /dev/null +++ b/src/main/java/carpet/network/CarpetClient.java @@ -0,0 +1,52 @@ +package carpet.network; + +import carpet.CarpetServer; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.Identifier; + +public class CarpetClient +{ + public static final Object sync = new Object(); + public static final int HI = 69; + public static final int HELLO = 420; + public static final int DATA = 1; + + private static ClientPlayerEntity clientPlayer = null; + private static boolean isServerCarpet = false; + public static String serverCarpetVersion; + public static final Identifier CARPET_CHANNEL = new Identifier("carpet:hello"); + + public static void gameJoined(ClientPlayerEntity player) + { + synchronized (sync) + { + clientPlayer = player; + // client didn't say hi back yet + if (isServerCarpet) + ClientNetworkHandler.respondHello(); + + } + } + + public static void disconnect() + { + isServerCarpet = false; + clientPlayer = null; + CarpetServer.disconnect(); + } + + public static void setCarpet() + { + isServerCarpet = true; + } + + public static ClientPlayerEntity getPlayer() + { + return clientPlayer; + } + + public static boolean isCarpet() + { + return isServerCarpet; + } +} diff --git a/src/main/java/carpet/network/ClientNetworkHandler.java b/src/main/java/carpet/network/ClientNetworkHandler.java new file mode 100644 index 00000000..68e2349a --- /dev/null +++ b/src/main/java/carpet/network/ClientNetworkHandler.java @@ -0,0 +1,93 @@ +package carpet.network; + +import carpet.CarpetServer; +import carpet.CarpetSettings; +import carpet.helpers.TickSpeed; +import carpet.settings.ParsedRule; +import io.netty.buffer.Unpooled; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.nbt.AbstractNumberTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; +import net.minecraft.util.PacketByteBuf; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public class ClientNetworkHandler +{ + private static Map> dataHandlers = new HashMap>(){{ + put("Rules", (p, t) -> { + CompoundTag ruleset = (CompoundTag)t; + for (String ruleName: ruleset.getKeys()) + { + ParsedRule rule = CarpetServer.settingsManager.getRule(ruleName); + if (rule == null) + CarpetSettings.LOG.error("Received unknown rule: "+ruleName); + else + { + CompoundTag ruleNBT = (CompoundTag) ruleset.get(ruleName); + String value = ruleNBT.getString("Value"); + rule.set(null, value); + } + } + }); + put("TickRate", (p, t) -> TickSpeed.tickrate(((AbstractNumberTag)t).getFloat(), false)); + }}; + + + public static void handleData(PacketByteBuf data, ClientPlayerEntity player) + { + if (data != null) + { + int id = data.readVarInt(); + if (id == CarpetClient.HI) + onHi(data); + if (id == CarpetClient.DATA) + onSyncData(data, player); + } + } + + private static void onHi(PacketByteBuf data) + { + synchronized (CarpetClient.sync) + { + CarpetClient.setCarpet(); + CarpetClient.serverCarpetVersion = data.readString(64); + if (CarpetSettings.carpetVersion.equals(CarpetClient.serverCarpetVersion)) + { + CarpetSettings.LOG.info("Joined carpet server with matching carpet version"); + } + else + { + CarpetSettings.LOG.warn("Joined carpet server with another carpet version: "+CarpetClient.serverCarpetVersion); + } + if (CarpetClient.getPlayer() != null) + respondHello(); + + } + } + + public static void respondHello() + { + CarpetClient.getPlayer().networkHandler.sendPacket(new CustomPayloadC2SPacket( + CarpetClient.CARPET_CHANNEL, + (new PacketByteBuf(Unpooled.buffer())).writeVarInt(CarpetClient.HELLO).writeString(CarpetSettings.carpetVersion) + )); + } + + private static void onSyncData(PacketByteBuf data, ClientPlayerEntity player) + { + CompoundTag compound = data.readCompoundTag(); + if (compound == null) return; + for (String key: compound.getKeys()) + { + if (dataHandlers.containsKey(key)) + dataHandlers.get(key).accept(player, compound.get(key)); + else + CarpetSettings.LOG.error("Unknown carpet data: "+key); + } + } +} diff --git a/src/main/java/carpet/network/ServerNetworkHandler.java b/src/main/java/carpet/network/ServerNetworkHandler.java new file mode 100644 index 00000000..152fcc37 --- /dev/null +++ b/src/main/java/carpet/network/ServerNetworkHandler.java @@ -0,0 +1,123 @@ +package carpet.network; + +import carpet.CarpetServer; +import carpet.CarpetSettings; +import carpet.helpers.TickSpeed; +import carpet.settings.ParsedRule; +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.PacketByteBuf; + +import java.util.ArrayList; +import java.util.List; + +public class ServerNetworkHandler +{ + private static List carpetPlayers = new ArrayList<>(); + + public static void handleData(PacketByteBuf data, ServerPlayerEntity player) + { + if (data != null) + { + int id = data.readVarInt(); + if (id == CarpetClient.HELLO) + onHello(player, data); + } + } + public static void onPlayerJoin(ServerPlayerEntity playerEntity) + { + if (!playerEntity.networkHandler.connection.isLocal()) + { + playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket( + CarpetClient.CARPET_CHANNEL, + (new PacketByteBuf(Unpooled.buffer())).writeVarInt(CarpetClient.HI).writeString(CarpetSettings.carpetVersion) + )); + } + } + + public static void onHello(ServerPlayerEntity playerEntity, PacketByteBuf packetData) + { + carpetPlayers.add(playerEntity); + String clientVersion = packetData.readString(64); + if (clientVersion.equals(CarpetSettings.carpetVersion)) + CarpetSettings.LOG.info("Player "+playerEntity.getName().getString()+" joined with a matching carpet client"); + else + CarpetSettings.LOG.warn("Player "+playerEntity.getName().getString()+" joined with another carpet version: "+clientVersion); + DataBuilder data = DataBuilder.create().withTickRate(); + CarpetServer.settingsManager.getRules().forEach(data::withRule); + playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetClient.CARPET_CHANNEL, data.build() )); + } + + public static void updateRuleWithConnectedClients(ParsedRule rule) + { + for (ServerPlayerEntity player : carpetPlayers) + { + player.networkHandler.sendPacket(new CustomPayloadS2CPacket( + CarpetClient.CARPET_CHANNEL, + DataBuilder.create().withRule(rule).build() + )); + } + } + + public static void updateTickSpeedToConnectedPlayers() + { + for (ServerPlayerEntity player : carpetPlayers) + { + player.networkHandler.sendPacket(new CustomPayloadS2CPacket( + CarpetClient.CARPET_CHANNEL, + DataBuilder.create().withTickRate().build() + )); + } + } + + public static void onPlayerLoggedOut(ServerPlayerEntity player) + { + if (!player.networkHandler.connection.isLocal()) + carpetPlayers.remove(player); + } + + public static void close() + { + carpetPlayers.clear(); + } + + private static class DataBuilder + { + private CompoundTag tag; + private static DataBuilder create() + { + return new DataBuilder(); + } + private DataBuilder() + { + tag = new CompoundTag(); + } + private DataBuilder withTickRate() + { + tag.putFloat("TickRate", TickSpeed.tickrate); + return this; + } + private DataBuilder withRule(ParsedRule rule) + { + CompoundTag rules = (CompoundTag) tag.get("Rules"); + if (rules == null) + { + rules = new CompoundTag(); + tag.put("Rules", rules); + } + CompoundTag ruleNBT = new CompoundTag(); + ruleNBT.putString("Value", rule.getAsString()); + rules.put(rule.name, ruleNBT); + return this; + } + private PacketByteBuf build() + { + PacketByteBuf packetBuf = new PacketByteBuf(Unpooled.buffer()); + packetBuf.writeVarInt(CarpetClient.DATA); + packetBuf.writeCompoundTag(tag); + return packetBuf; + } + } +} diff --git a/src/main/java/carpet/settings/SettingsManager.java b/src/main/java/carpet/settings/SettingsManager.java index 93aac4df..5765f422 100644 --- a/src/main/java/carpet/settings/SettingsManager.java +++ b/src/main/java/carpet/settings/SettingsManager.java @@ -2,6 +2,7 @@ package carpet.settings; import carpet.CarpetServer; import carpet.CarpetSettings; +import carpet.network.ServerNetworkHandler; import carpet.utils.Translations; import carpet.utils.Messenger; import com.google.common.collect.ImmutableList; @@ -80,7 +81,7 @@ public class SettingsManager public void detachServer() { - for (ParsedRule rule : rules.values()) rule.resetToDefault(server.getCommandSource()); + for (ParsedRule rule : rules.values()) rule.resetToDefault(null); server = null; } @@ -103,6 +104,7 @@ public class SettingsManager void notifyRuleChanged(ServerCommandSource source, ParsedRule rule, String userTypedValue) { observers.forEach(observer -> observer.accept(source, rule, userTypedValue)); + ServerNetworkHandler.updateRuleWithConnectedClients(rule); } public Iterable getCategories() @@ -584,8 +586,6 @@ public class SettingsManager rules.get(setting).set(source, strOption); } } - - } } } diff --git a/src/main/java/carpet/settings/Validator.java b/src/main/java/carpet/settings/Validator.java index 8b561ff6..3f48fff0 100644 --- a/src/main/java/carpet/settings/Validator.java +++ b/src/main/java/carpet/settings/Validator.java @@ -22,7 +22,7 @@ public abstract class Validator @Override public T validate(ServerCommandSource source, ParsedRule currentRule, T newValue, String string) { - if (CarpetServer.settingsManager != null) + if (CarpetServer.settingsManager != null && source != null) CarpetServer.settingsManager.notifyPlayersCommandsChanged(); return newValue; } diff --git a/src/main/java/carpet/utils/Messenger.java b/src/main/java/carpet/utils/Messenger.java index 78e7fddc..7aa4f7b5 100644 --- a/src/main/java/carpet/utils/Messenger.java +++ b/src/main/java/carpet/utils/Messenger.java @@ -231,7 +231,8 @@ public class Messenger //message source public static void m(ServerCommandSource source, Object ... fields) { - source.sendFeedback(Messenger.c(fields),source.getMinecraftServer() != null && source.getMinecraftServer().getWorld(DimensionType.OVERWORLD) != null); + if (source != null) + source.sendFeedback(Messenger.c(fields),source.getMinecraftServer() != null && source.getMinecraftServer().getWorld(DimensionType.OVERWORLD) != null); } public static void m(PlayerEntity player, Object ... fields) { diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 041dbaaa..247b24d1 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -6,6 +6,9 @@ "mixins": [ "CommandManagerMixin", + "ServerPlayNetworkHandlerMixin", + "CustomPayloadC2SPacketMixin", + "FillCommandMixin", "CloneCommandMixin", "SetBlockCommandMixin", From cf18aa3fcfa29caf18ffd7eeed2779f779c56fe5 Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 28 May 2020 09:40:50 -0300 Subject: [PATCH 3/7] mod compat --- src/main/java/carpet/helpers/TickSpeed.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java index 091c7c73..dd642112 100644 --- a/src/main/java/carpet/helpers/TickSpeed.java +++ b/src/main/java/carpet/helpers/TickSpeed.java @@ -177,7 +177,8 @@ public class TickSpeed } } } - + //unused - mod compat reasons + public static void tickrate(float rate) {tickrate(rate, true);} public static void tickrate(float rate, boolean update) { tickrate = rate; From cfe1b7d837179318d4a1a7c133fe041e2429f7ca Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 29 May 2020 14:05:37 -0300 Subject: [PATCH 4/7] docs on time --- docs/scarpet/language/SystemFunctions.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md index 7c506921..a1efa6d6 100644 --- a/docs/scarpet/language/SystemFunctions.md +++ b/docs/scarpet/language/SystemFunctions.md @@ -263,8 +263,11 @@ sleep(50) ### `time()` -Returns the number of milliseconds since 'some point', like Java's `System.nanoTime()`. -It returns a float, which has 1 microsecond precision (0.001 ms) +Returns the number of milliseconds since 'some point', like Java's `System.nanoTime()`, which varies from system to +system and from Java to Java. This measure should NOT be used to determine the current (date)time, but to measure +durations of things. +it returns a float with time in milliseconds (ms) for convenience and microsecond (μs) resolution for sanity. +
 start_time = time();

From 7c0a377d711ebc1861aa9c319f6effdbb476b44a Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Fri, 29 May 2020 14:06:32 -0300
Subject: [PATCH 5/7] shapes renderer WIP

---
 .../carpet/script/utils/ShapesRenderer.java   | 53 +++++++++++++++++++
 1 file changed, 53 insertions(+)
 create mode 100644 src/main/java/carpet/script/utils/ShapesRenderer.java

diff --git a/src/main/java/carpet/script/utils/ShapesRenderer.java b/src/main/java/carpet/script/utils/ShapesRenderer.java
new file mode 100644
index 00000000..528be080
--- /dev/null
+++ b/src/main/java/carpet/script/utils/ShapesRenderer.java
@@ -0,0 +1,53 @@
+package carpet.script.utils;
+
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.List;
+
+public class ShapesRenderer
+{
+    List shapes;
+
+    void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, double cameraX, double cameraY, double cameraZ){
+
+    }
+    void addShape() {}
+
+
+    public abstract static class ExpiringShape
+    {
+        protected int r;
+        protected int g;
+        protected int b;
+        protected int a;
+        protected int expiryTick = 0;
+        public abstract void render(MatrixStack matrix, VertexConsumer vertexConsumer);
+        public ExpiringShape expiresIn(int ticks)
+        {
+            expiryTick = ticks;
+            return this;
+        }
+
+    }
+
+    public static class SolidLine extends ExpiringShape
+    {
+        public SolidLine(Vec3d from, Vec3d to, int r, int g, int b, int a)
+        {
+            this.r = r;
+            this.g = g;
+            this.b = b;
+            this.a = a;
+
+        }
+        @Override
+        public void render(MatrixStack matrix, VertexConsumer vertexConsumer)
+        {
+
+        }
+    }
+
+}

From f6fb8e58991f9468a4a849d0053b34df650dece7 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 31 May 2020 01:06:41 -0300
Subject: [PATCH 6/7] ++ got eaten

---
 src/main/java/carpet/settings/ParsedRule.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/carpet/settings/ParsedRule.java b/src/main/java/carpet/settings/ParsedRule.java
index e2acdd25..f25b0a38 100644
--- a/src/main/java/carpet/settings/ParsedRule.java
+++ b/src/main/java/carpet/settings/ParsedRule.java
@@ -253,6 +253,7 @@ public final class ParsedRule implements Comparable {
         while (Translations.hasTranslation(keyBase+i))
         {
             extras.add(Translations.tr(keyBase+i));
+            i++;
         }
         return (extras.isEmpty()) ? extraInfo : extras;
     }

From 47452b5b12cb2e0b23d5304785d8631fe85e3fe9 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 31 May 2020 01:57:04 -0300
Subject: [PATCH 7/7] added marker lines and boxes with client synchro fixes.

---
 docs/scarpet/Full.md                          |   2 +-
 docs/scarpet/api/Auxiliary.md                 |  28 +-
 .../DebugRenderer_scarpetRenderMixin.java     |  38 +++
 .../java/carpet/network/CarpetClient.java     |   2 +
 .../carpet/network/ClientNetworkHandler.java  |  12 +
 .../carpet/network/ServerNetworkHandler.java  |  58 +++-
 .../java/carpet/script/CarpetExpression.java  | 271 +++++++++-------
 src/main/java/carpet/script/Expression.java   |   6 +
 .../carpet/script/utils/ShapeDispatcher.java  | 289 ++++++++++++++++++
 .../carpet/script/utils/ShapesRenderer.java   | 170 +++++++++--
 .../assets/carpet/scripts/overlay.sc          |  45 ++-
 src/main/resources/carpet.mixins.json         |   4 +-
 12 files changed, 752 insertions(+), 173 deletions(-)
 create mode 100644 src/main/java/carpet/mixins/DebugRenderer_scarpetRenderMixin.java
 create mode 100644 src/main/java/carpet/script/utils/ShapeDispatcher.java

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index b8c7ae9c..99523a4f 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -1513,7 +1513,7 @@ range(5,10)  => [5, 6, 7, 8, 9]
 range(20, 10, -2)  => [20, 18, 16, 14, 12]
 
-### `element(list, index)(deprecated)` +### `element(list, index)` (deprecated) Legacy support for older method that worked only on lists. Please use `get(...)` for equivalent support, or `.` operator. Also previous unique behaviours with `put` on lists has been removed to support all type of diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index a3ea3296..7d9da627 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -14,24 +14,42 @@ and `voice`. `pos` can be either a block, triple of coords, or a list of thee nu ## Particles -### `particle(name, pos, count?. spread?, speed?, playername?)` +### `particle(name, pos, count?. spread?, speed?, player?)` Renders a cloud of particles `name` centered around `pos` position, by default `count` 10 of them, default `speed` of 0, and to all players nearby, but these options can be changed via optional arguments. Follow vanilla `/particle` command on details on those options. Valid particle names are for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`. -### `particle_line(name, pos, pos2, density?)` +### `particle_line(name, pos, pos2, density?, player?)` Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far -apart you would want particles to appear, so `0.1` means one every 10cm. +apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only +that player will receive particles. -### `particle_rect(name, pos, pos2, density?)` -Renders a cuboid of particles between points `pos` and `pos2` with supplied density. +### `particle_box(name, pos, pos2, density?, player?)` +### `particle_rect` (deprecated) + +Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is +supplied, only that player will receive particles. ## Markers +### `marker_box(from, to, duration?, color?, player?)` + +Draws a box with `from` and `to` corners in the box. The boxes will dissipate after `duration` ticks (defaults to 10). +The color is an integer that indicate red, green, blue and alpha components in the form of `0xRRGGBBAA`, with the +default of `-1`, so white opaque, or `0xFFFFFFFF`. If player is specified, only displays for that player, otherwise all +players will see them. `marker_box` requires proper client synchronization to display these, and on non-supported +clients, the box will be replaced with close matching dust particles, so using of `marker_box` is safe to use for +scripts regardless who is targeted. The only consideration is that with vanilla clients, duration of particles cannot +be controlled, and last whatever the original particles do. + +### `marker_line(from, to, duration?, color?, player?)` + +Draws a marker line on client's screen. Uses similar setup as `marker_box`. + ### `create_marker(text, pos, rotation?, block?)` Spawns a (permanent) marker entity with text or block at position. Returns that entity for further manipulations. diff --git a/src/main/java/carpet/mixins/DebugRenderer_scarpetRenderMixin.java b/src/main/java/carpet/mixins/DebugRenderer_scarpetRenderMixin.java new file mode 100644 index 00000000..a57a3b26 --- /dev/null +++ b/src/main/java/carpet/mixins/DebugRenderer_scarpetRenderMixin.java @@ -0,0 +1,38 @@ +package carpet.mixins; + +import carpet.network.CarpetClient; +import carpet.script.utils.ShapesRenderer; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.debug.DebugRenderer; +import net.minecraft.client.util.math.MatrixStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(DebugRenderer.class) +public class DebugRenderer_scarpetRenderMixin +{ + private ShapesRenderer shapes; + + @Inject(method = "", at = @At("RETURN")) + private void addRenderers(MinecraftClient client, CallbackInfo ci) + { + shapes = new ShapesRenderer(client); + CarpetClient.shapes = shapes; + } + + @Inject(method = "reset", at = @At("HEAD")) + private void resetScarpetRenderes(CallbackInfo ci) + { + shapes.reset(); + } + + @Inject(method = "render", at = @At("HEAD")) + private void renderScarpetThings(MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers, double cameraX, double cameraY, double cameraZ, CallbackInfo ci) + { + shapes.render(matrices, vertexConsumers, cameraX, cameraY, cameraZ); + } + +} diff --git a/src/main/java/carpet/network/CarpetClient.java b/src/main/java/carpet/network/CarpetClient.java index 0846a9d0..7010eb31 100644 --- a/src/main/java/carpet/network/CarpetClient.java +++ b/src/main/java/carpet/network/CarpetClient.java @@ -1,6 +1,7 @@ package carpet.network; import carpet.CarpetServer; +import carpet.script.utils.ShapesRenderer; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.util.Identifier; @@ -10,6 +11,7 @@ public class CarpetClient public static final int HI = 69; public static final int HELLO = 420; public static final int DATA = 1; + public static ShapesRenderer shapes = null; private static ClientPlayerEntity clientPlayer = null; private static boolean isServerCarpet = false; diff --git a/src/main/java/carpet/network/ClientNetworkHandler.java b/src/main/java/carpet/network/ClientNetworkHandler.java index 68e2349a..7c1336c3 100644 --- a/src/main/java/carpet/network/ClientNetworkHandler.java +++ b/src/main/java/carpet/network/ClientNetworkHandler.java @@ -11,6 +11,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; import net.minecraft.util.PacketByteBuf; +import net.minecraft.world.dimension.DimensionType; import java.util.HashMap; import java.util.Map; @@ -35,6 +36,17 @@ public class ClientNetworkHandler } }); put("TickRate", (p, t) -> TickSpeed.tickrate(((AbstractNumberTag)t).getFloat(), false)); + put("renderShape", (p, t) -> { + if (CarpetClient.shapes != null) + { + CompoundTag boxData = (CompoundTag)t; + CarpetClient.shapes.addShape( + boxData.getString("type"), + DimensionType.byRawId(boxData.getInt("dim")), + boxData + ); + } + }); }}; diff --git a/src/main/java/carpet/network/ServerNetworkHandler.java b/src/main/java/carpet/network/ServerNetworkHandler.java index 152fcc37..b609dd7d 100644 --- a/src/main/java/carpet/network/ServerNetworkHandler.java +++ b/src/main/java/carpet/network/ServerNetworkHandler.java @@ -6,16 +6,18 @@ import carpet.helpers.TickSpeed; import carpet.settings.ParsedRule; import io.netty.buffer.Unpooled; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.PacketByteBuf; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; public class ServerNetworkHandler { - private static List carpetPlayers = new ArrayList<>(); + public static Set remoteCarpetPlayers = new HashSet<>(); + public static Set validCarpetPlayers = new HashSet<>(); public static void handleData(PacketByteBuf data, ServerPlayerEntity player) { @@ -35,11 +37,17 @@ public class ServerNetworkHandler (new PacketByteBuf(Unpooled.buffer())).writeVarInt(CarpetClient.HI).writeString(CarpetSettings.carpetVersion) )); } + else + { + validCarpetPlayers.add(playerEntity); + } + } public static void onHello(ServerPlayerEntity playerEntity, PacketByteBuf packetData) { - carpetPlayers.add(playerEntity); + remoteCarpetPlayers.add(playerEntity); + validCarpetPlayers.add(playerEntity); String clientVersion = packetData.readString(64); if (clientVersion.equals(CarpetSettings.carpetVersion)) CarpetSettings.LOG.info("Player "+playerEntity.getName().getString()+" joined with a matching carpet client"); @@ -52,7 +60,7 @@ public class ServerNetworkHandler public static void updateRuleWithConnectedClients(ParsedRule rule) { - for (ServerPlayerEntity player : carpetPlayers) + for (ServerPlayerEntity player : remoteCarpetPlayers) { player.networkHandler.sendPacket(new CustomPayloadS2CPacket( CarpetClient.CARPET_CHANNEL, @@ -63,7 +71,7 @@ public class ServerNetworkHandler public static void updateTickSpeedToConnectedPlayers() { - for (ServerPlayerEntity player : carpetPlayers) + for (ServerPlayerEntity player : remoteCarpetPlayers) { player.networkHandler.sendPacket(new CustomPayloadS2CPacket( CarpetClient.CARPET_CHANNEL, @@ -72,15 +80,40 @@ public class ServerNetworkHandler } } + public static void sendCustomCommand(String command, Tag data) + { + for (ServerPlayerEntity player : validCarpetPlayers) + { + player.networkHandler.sendPacket(new CustomPayloadS2CPacket( + CarpetClient.CARPET_CHANNEL, + DataBuilder.create().withCustomNbt(command, data).build() + )); + } + } + + public static void sendCustomCommand(ServerPlayerEntity player, String command, Tag data) + { + if (validCarpetPlayers.contains(player)) + { + player.networkHandler.sendPacket(new CustomPayloadS2CPacket( + CarpetClient.CARPET_CHANNEL, + DataBuilder.create().withCustomNbt(command, data).build() + )); + } + } + + public static void onPlayerLoggedOut(ServerPlayerEntity player) { + validCarpetPlayers.remove(player); if (!player.networkHandler.connection.isLocal()) - carpetPlayers.remove(player); + remoteCarpetPlayers.remove(player); } public static void close() { - carpetPlayers.clear(); + remoteCarpetPlayers.clear(); + validCarpetPlayers.clear(); } private static class DataBuilder @@ -112,6 +145,13 @@ public class ServerNetworkHandler rules.put(rule.name, ruleNBT); return this; } + + public DataBuilder withCustomNbt(String key, Tag value) + { + tag.put(key, value); + return this; + } + private PacketByteBuf build() { PacketByteBuf packetBuf = new PacketByteBuf(Unpooled.buffer()); @@ -119,5 +159,7 @@ public class ServerNetworkHandler packetBuf.writeCompoundTag(tag); return packetBuf; } + + } } diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index d1329558..42bad5a4 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -21,6 +21,7 @@ import carpet.script.exception.ContinueStatement; import carpet.script.exception.ExitStatement; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; +import carpet.script.utils.ShapeDispatcher; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; import carpet.script.value.FormattedTextValue; @@ -63,7 +64,6 @@ import net.minecraft.item.TridentItem; import net.minecraft.network.packet.s2c.play.ContainerSlotUpdateS2CPacket; import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; import net.minecraft.command.arguments.ItemStackArgument; -import net.minecraft.command.arguments.ParticleArgumentType; import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -148,7 +148,6 @@ import static carpet.script.utils.WorldTools.canHasChunk; import static java.lang.Math.abs; import static java.lang.Math.max; import static java.lang.Math.min; -import static java.lang.Math.sqrt; public class CarpetExpression { @@ -261,90 +260,6 @@ public class CarpetExpression return bs; } - private static final Map particleCache = new HashMap<>(); - private ParticleEffect getParticleData(String name) - { - ParticleEffect particle = particleCache.get(name); - if (particle != null) - return particle; - try - { - particle = ParticleArgumentType.readParameters(new StringReader(name)); - } - catch (CommandSyntaxException e) - { - throw new InternalExpressionException("No such particle: "+name); - } - particleCache.put(name, particle); - return particle; - } - - - private boolean isStraight(Vec3d from, Vec3d to, double density) - { - if ( (from.x == to.x && from.y == to.y) || (from.x == to.x && from.z == to.z) || (from.y == to.y && from.z == to.z)) - return from.distanceTo(to) / density > 20; - return false; - } - - private int drawOptimizedParticleLine(ServerWorld world, ParticleEffect particle, Vec3d from, Vec3d to, double density) - { - double distance = from.distanceTo(to); - int particles = (int)(distance/density); - Vec3d towards = to.subtract(from); - int parts = 0; - for (PlayerEntity player : world.getPlayers()) - { - world.spawnParticles((ServerPlayerEntity)player, particle, true, - (towards.x)/2+from.x, (towards.y)/2+from.y, (towards.z)/2+from.z, particles/3, - towards.x/6, towards.y/6, towards.z/6, 0.0); - world.spawnParticles((ServerPlayerEntity)player, particle, true, - from.x, from.y, from.z,1,0.0,0.0,0.0,0.0); - world.spawnParticles((ServerPlayerEntity)player, particle, true, - to.x, to.y, to.z,1,0.0,0.0,0.0,0.0); - parts += particles/3+2; - } - int divider = 6; - while (particles/divider > 1) - { - int center = (divider*2)/3; - int dev = 2*divider; - for (PlayerEntity player : world.getPlayers()) - { - world.spawnParticles((ServerPlayerEntity)player, particle, true, - (towards.x)/center+from.x, (towards.y)/center+from.y, (towards.z)/center+from.z, particles/divider, - towards.x/dev, towards.y/dev, towards.z/dev, 0.0); - world.spawnParticles((ServerPlayerEntity)player, particle, true, - (towards.x)*(1.0-1.0/center)+from.x, (towards.y)*(1.0-1.0/center)+from.y, (towards.z)*(1.0-1.0/center)+from.z, particles/divider, - towards.x/dev, towards.y/dev, towards.z/dev, 0.0); - } - parts += 2*particles/divider; - divider = 2*divider; - } - return parts; - } - - private int drawParticleLine(ServerWorld world, ParticleEffect particle, Vec3d from, Vec3d to, double density) - { - if (isStraight(from, to, density)) return drawOptimizedParticleLine(world, particle, from, to, density); - double lineLengthSq = from.squaredDistanceTo(to); - if (lineLengthSq == 0) return 0; - Vec3d incvec = to.subtract(from).multiply(2*density/sqrt(lineLengthSq)); - int pcount = 0; - for (Vec3d delta = new Vec3d(0.0,0.0,0.0); - delta.lengthSquared() offset)? NumericValue.asNumber(lv.get(offset).evalValue(c)).getDouble():1.0; - if (density <= 0) + double density = 1.0; + ServerPlayerEntity player = null; + if (lv.size() > pos2.offset+0 ) { - throw new InternalExpressionException("Particle density should be positive"); + density = NumericValue.asNumber(lv.get(pos2.offset+0).evalValue(c)).getDouble(); + if (density <= 0) + { + throw new InternalExpressionException("Particle density should be positive"); + } + if (lv.size() > pos2.offset+1) + { + Value playerValue = lv.get(pos2.offset+1).evalValue(c); + if (playerValue instanceof EntityValue) + { + Entity e = ((EntityValue) playerValue).getEntity(); + if (!(e instanceof ServerPlayerEntity)) throw new InternalExpressionException("'particle_line' player argument has to be a player"); + player = (ServerPlayerEntity) e; + } + else + { + player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString()); + } + } } - Value retval = new NumericValue(drawParticleLine(world, particle, pos1.vec, pos2.vec, density)); + + Value retval = new NumericValue(ShapeDispatcher.drawParticleLine( + (player == null)?world.getPlayers():Collections.singletonList(player), + particle, pos1.vec, pos2.vec, density)); + return (c_, t_) -> retval; }); - this.expr.addLazyFunction("particle_rect", -1, (c, t, lv) -> + this.expr.addLazyFunction("particle_box", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; ServerWorld world = cc.s.getWorld(); String particleName = lv.get(0).evalValue(c).getString(); - ParticleEffect particle = getParticleData(particleName); + ParticleEffect particle = ShapeDispatcher.getParticleData(particleName); Vector3Argument pos1 = Vector3Argument.findIn(cc, lv, 1); Vector3Argument pos2 = Vector3Argument.findIn(cc, lv, pos1.offset); - int offset = pos2.offset; + double density = 1.0; - if (lv.size() > offset) + ServerPlayerEntity player = null; + if (lv.size() > pos2.offset+0 ) { - density = NumericValue.asNumber(lv.get(offset).evalValue(c)).getDouble(); - } - if (density <= 0) - { - throw new InternalExpressionException("Particle density should be positive"); + density = NumericValue.asNumber(lv.get(pos2.offset+0).evalValue(c)).getDouble(); + if (density <= 0) + { + throw new InternalExpressionException("Particle density should be positive"); + } + if (lv.size() > pos2.offset+1) + { + Value playerValue = lv.get(pos2.offset+1).evalValue(c); + if (playerValue instanceof EntityValue) + { + Entity e = ((EntityValue) playerValue).getEntity(); + if (!(e instanceof ServerPlayerEntity)) throw new InternalExpressionException("'particle_box' player argument has to be a player"); + player = (ServerPlayerEntity) e; + } + else + { + player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString()); + } + } } Vec3d a = pos1.vec; Vec3d b = pos2.vec; @@ -2689,24 +2641,107 @@ public class CarpetExpression double bx = max(a.x, b.x); double by = max(a.y, b.y); double bz = max(a.z, b.z); - int pc = 0; - pc += drawParticleLine(world, particle, new Vec3d(ax, ay, az), new Vec3d(ax, by, az), density); - pc += drawParticleLine(world, particle, new Vec3d(ax, by, az), new Vec3d(bx, by, az), density); - pc += drawParticleLine(world, particle, new Vec3d(bx, by, az), new Vec3d(bx, ay, az), density); - pc += drawParticleLine(world, particle, new Vec3d(bx, ay, az), new Vec3d(ax, ay, az), density); - - pc += drawParticleLine(world, particle, new Vec3d(ax, ay, bz), new Vec3d(ax, by, bz), density); - pc += drawParticleLine(world, particle, new Vec3d(ax, by, bz), new Vec3d(bx, by, bz), density); - pc += drawParticleLine(world, particle, new Vec3d(bx, by, bz), new Vec3d(bx, ay, bz), density); - pc += drawParticleLine(world, particle, new Vec3d(bx, ay, bz), new Vec3d(ax, ay, bz), density); - - pc += drawParticleLine(world, particle, new Vec3d(ax, ay, az), new Vec3d(ax, ay, bz), density); - pc += drawParticleLine(world, particle, new Vec3d(ax, by, az), new Vec3d(ax, by, bz), density); - pc += drawParticleLine(world, particle, new Vec3d(bx, by, az), new Vec3d(bx, by, bz), density); - pc += drawParticleLine(world, particle, new Vec3d(bx, ay, az), new Vec3d(bx, ay, bz), density); - int particleCount = pc; + int particleCount = ShapeDispatcher.Box.mesh( + player==null?world.getPlayers():Collections.singletonList(player), + particle, density, ax, ay, az, bx, by, bz + ); return (c_, t_) -> new NumericValue(particleCount); }); + // deprecated + this.expr.alias("particle_rect", "particle_box"); + + + this.expr.addLazyFunction("marker_box", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + Vector3Argument from = Vector3Argument.findIn(cc, lv, 0); + Vector3Argument to = Vector3Argument.findIn(cc, lv, from.offset); + int duration = 10; + int color = -1; // hwite + ServerPlayerEntity player = null; + + if (lv.size() > to.offset+0 ) + { + duration = NumericValue.asNumber(lv.get(to.offset).evalValue(c)).getInt(); + if (duration <= 0) + { + throw new InternalExpressionException("Particle density should be positive"); + } + if (lv.size() > to.offset + 1) + { + color = NumericValue.asNumber(lv.get(to.offset+1).evalValue(c)).getInt(); + + + if (lv.size() > to.offset + 2) + { + Value playerValue = lv.get(to.offset + 2).evalValue(c); + if (playerValue instanceof EntityValue) + { + Entity e = ((EntityValue) playerValue).getEntity(); + if (!(e instanceof ServerPlayerEntity)) + throw new InternalExpressionException("'particle_box' player argument has to be a player"); + player = (ServerPlayerEntity) e; + } + else + { + player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString()); + } + } + } + } + ShapeDispatcher.sendShape( + (player==null)?cc.s.getWorld().getPlayers():Collections.singletonList(player), + cc.s.getWorld().getDimension().getType(), + new ShapeDispatcher.Box(duration, from.vec, to.vec, color) + ); + return LazyValue.TRUE; + }); + + this.expr.addLazyFunction("marker_line", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + Vector3Argument from = Vector3Argument.findIn(cc, lv, 0); + Vector3Argument to = Vector3Argument.findIn(cc, lv, from.offset); + int duration = 10; + int color = -1; // hwite + ServerPlayerEntity player = null; + + if (lv.size() > to.offset+0 ) + { + duration = NumericValue.asNumber(lv.get(to.offset).evalValue(c)).getInt(); + if (duration <= 0) + { + throw new InternalExpressionException("Particle density should be positive"); + } + if (lv.size() > to.offset + 1) + { + color = NumericValue.asNumber(lv.get(to.offset+1).evalValue(c)).getInt(); + + + if (lv.size() > to.offset + 2) + { + Value playerValue = lv.get(to.offset + 2).evalValue(c); + if (playerValue instanceof EntityValue) + { + Entity e = ((EntityValue) playerValue).getEntity(); + if (!(e instanceof ServerPlayerEntity)) + throw new InternalExpressionException("'particle_box' player argument has to be a player"); + player = (ServerPlayerEntity) e; + } + else + { + player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString()); + } + } + } + } + ShapeDispatcher.sendShape( + (player==null)?cc.s.getWorld().getPlayers():Collections.singletonList(player), + cc.s.getWorld().getDimension().getType(), + new ShapeDispatcher.Line(duration, from.vec, to.vec, color) + ); + return LazyValue.TRUE; + }); this.expr.addLazyFunction("create_marker", -1, (c, t, lv) ->{ CarpetContext cc = (CarpetContext)c; diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 83e607fe..a53f4eb5 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -418,6 +418,12 @@ public class Expression return result; } + public void alias(String copy, String original) + { + functions.put(copy, functions.get(original)); + } + + private void UserDefinedFunctionsAndControlFlow() // public just to get the javadoc right { // artificial construct to handle user defined functions and function definitions diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java new file mode 100644 index 00000000..4a6c8c7c --- /dev/null +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -0,0 +1,289 @@ +package carpet.script.utils; + +import carpet.network.ServerNetworkHandler; +import carpet.script.Expression; +import carpet.script.exception.InternalExpressionException; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.command.arguments.ParticleArgumentType; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.particle.ParticleEffect; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.dimension.DimensionType; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Consumer; + +import static java.lang.Math.sqrt; + +public class ShapeDispatcher +{ + private static final Map particleCache = new HashMap<>(); + + public static void sendShape(List players, DimensionType dim, ExpiringShape shape) + { + Consumer alternative = null; + CompoundTag tag = null; + for (ServerPlayerEntity player : players) + { + if (ServerNetworkHandler.validCarpetPlayers.contains(player)) + { + if (tag == null) tag = shape.toTag(); + tag.putInt("dim", dim.getRawId()); + ServerNetworkHandler.sendCustomCommand(player,"renderShape", tag); + } + else + { + if (alternative == null) alternative = shape.alternative(); + alternative.accept(player); + } + } + } + + public static void sendBox(ServerPlayerEntity player, int duration, Vec3d from, Vec3d to, int color) + { + + } + + public static ParticleEffect getParticleData(String name) + { + ParticleEffect particle = particleCache.get(name); + if (particle != null) + return particle; + try + { + particle = ParticleArgumentType.readParameters(new StringReader(name)); + } + catch (CommandSyntaxException e) + { + throw new InternalExpressionException("No such particle: "+name); + } + particleCache.put(name, particle); + return particle; + } + + + public abstract static class ExpiringShape + { + protected float r, g, b, a; + protected int color; + protected int duration = 0; + public ExpiringShape(int duration, int color) + { + this.r = (float)(color >> 24 & 0xFF) / 255.0F; + this.g = (float)(color >> 16 & 0xFF) / 255.0F; + this.b = (float)(color >> 8 & 0xFF) / 255.0F; + this.a = (float)(color & 0xFF) / 255.0F; + this.color = color; + this.duration = duration; + }; + + public int getExpiry() { return duration; }; + public CompoundTag toTag() + { + CompoundTag tag = new CompoundTag(); + tag.putInt("color", color); + tag.putInt("duration", duration); + return tag; + }; + public abstract Consumer alternative(); + } + + public static class Box extends ExpiringShape + { + float x1, y1, z1; + float x2, y2, z2; + public Box(int duration, Vec3d from, Vec3d to, int color) + { + super(duration, color); + x1 = (float)from.x; + y1 = (float)from.y; + z1 = (float)from.z; + x2 = (float)to.x; + y2 = (float)to.y; + z2 = (float)to.z; + } + + + public static ExpiringShape fromTag(CompoundTag tag) + { + return new Box(tag.getInt("duration"), + new Vec3d(tag.getFloat("x1"), tag.getFloat("y1"), tag.getFloat("z1")), + new Vec3d(tag.getFloat("x2"), tag.getFloat("y2"), tag.getFloat("z2")), + tag.getInt("color") + ); + } + + @Override + public CompoundTag toTag() + { + CompoundTag tag = super.toTag(); + tag.putFloat("x1", x1); + tag.putFloat("y1", y1); + tag.putFloat("z1", z1); + tag.putFloat("x2", x2); + tag.putFloat("y2", y2); + tag.putFloat("z2", z2); + tag.putString("type", "debugBox"); + return tag; + } + + @Override + public Consumer alternative() + { + String particleName = String.format(Locale.ROOT , "dust %.1f %.1f %.1f 1.0", r, g, b); + ParticleEffect particle = getParticleData(particleName); + double dx = x1-x2; + double dy = y1-y2; + double dz = z1-z2; + double density = Math.max(2.0, Math.sqrt(dx*dx+dy*dy+dz*dz) /50) / (a+0.1); + return p -> mesh(Collections.singletonList(p), particle, density, x1, y1, z1, x2, y2, z2); + } + + public static int mesh(List playerList, ParticleEffect particle, double density, + double x1, double y1, double z1, double x2, double y2, double z2 ) + { + return + drawParticleLine(playerList, particle, new Vec3d(x1, y1, z1), new Vec3d(x1, y2, z1), density)+ + drawParticleLine(playerList, particle, new Vec3d(x1, y2, z1), new Vec3d(x2, y2, z1), density)+ + drawParticleLine(playerList, particle, new Vec3d(x2, y2, z1), new Vec3d(x2, y1, z1), density)+ + drawParticleLine(playerList, particle, new Vec3d(x2, y1, z1), new Vec3d(x1, y1, z1), density)+ + + drawParticleLine(playerList, particle, new Vec3d(x1, y1, z2), new Vec3d(x1, y2, z2), density)+ + drawParticleLine(playerList, particle, new Vec3d(x1, y2, z2), new Vec3d(x2, y2, z2), density)+ + drawParticleLine(playerList, particle, new Vec3d(x2, y2, z2), new Vec3d(x2, y1, z2), density)+ + drawParticleLine(playerList, particle, new Vec3d(x2, y1, z2), new Vec3d(x1, y1, z2), density)+ + + drawParticleLine(playerList, particle, new Vec3d(x1, y1, z1), new Vec3d(x1, y1, z2), density)+ + drawParticleLine(playerList, particle, new Vec3d(x1, y2, z1), new Vec3d(x1, y2, z2), density)+ + drawParticleLine(playerList, particle, new Vec3d(x2, y2, z1), new Vec3d(x2, y2, z2), density)+ + drawParticleLine(playerList, particle, new Vec3d(x2, y1, z1), new Vec3d(x2, y1, z2), density); + } + } + + public static class Line extends ExpiringShape + { + float x1, y1, z1; + float x2, y2, z2; + public Line(int duration, Vec3d from, Vec3d to, int color) + { + super(duration, color); + x1 = (float)from.x; + y1 = (float)from.y; + z1 = (float)from.z; + x2 = (float)to.x; + y2 = (float)to.y; + z2 = (float)to.z; + + } + @Override + public CompoundTag toTag() + { + CompoundTag tag = super.toTag(); + tag.putFloat("x1", x1); + tag.putFloat("y1", y1); + tag.putFloat("z1", z1); + tag.putFloat("x2", x2); + tag.putFloat("y2", y2); + tag.putFloat("z2", z2); + tag.putString("type", "debugLine"); + return tag; + } + + public static ExpiringShape fromTag(CompoundTag tag) + { + return new Line(tag.getInt("duration"), + new Vec3d(tag.getFloat("x1"), tag.getFloat("y1"), tag.getFloat("z1")), + new Vec3d(tag.getFloat("x2"), tag.getFloat("y2"), tag.getFloat("z2")), + tag.getInt("color") + ); + } + @Override + public Consumer alternative() + { + String particleName = String.format(Locale.ROOT , "dust %.1f %.1f %.1f 1.0", r, g, b); + ParticleEffect particle = getParticleData(particleName); + Vec3d pos1 = new Vec3d(x1, y1, z1); + Vec3d pos2 = new Vec3d(x2, y2, z2); + double dx = x1-x2; + double dy = y1-y2; + double dz = z1-z2; + double density = Math.max(2.0, Math.sqrt(dx*dx+dy*dy+dz*dz) /50) / (a+0.1); + return p -> drawParticleLine(Collections.singletonList(p), particle, pos1, pos2, density); + } + } + + private static boolean isStraight(Vec3d from, Vec3d to, double density) + { + if ( (from.x == to.x && from.y == to.y) || (from.x == to.x && from.z == to.z) || (from.y == to.y && from.z == to.z)) + return from.distanceTo(to) / density > 20; + return false; + } + + private static int drawOptimizedParticleLine(List playerList, ParticleEffect particle, Vec3d from, Vec3d to, double density) + { + double distance = from.distanceTo(to); + int particles = (int)(distance/density); + Vec3d towards = to.subtract(from); + int parts = 0; + for (ServerPlayerEntity player : playerList) + { + ServerWorld world = player.getServerWorld(); + world.spawnParticles(player, particle, true, + (towards.x)/2+from.x, (towards.y)/2+from.y, (towards.z)/2+from.z, particles/3, + towards.x/6, towards.y/6, towards.z/6, 0.0); + world.spawnParticles(player, particle, true, + from.x, from.y, from.z,1,0.0,0.0,0.0,0.0); + world.spawnParticles(player, particle, true, + to.x, to.y, to.z,1,0.0,0.0,0.0,0.0); + parts += particles/3+2; + } + int divider = 6; + while (particles/divider > 1) + { + int center = (divider*2)/3; + int dev = 2*divider; + for (ServerPlayerEntity player : playerList) + { + ServerWorld world = player.getServerWorld(); + world.spawnParticles(player, particle, true, + (towards.x)/center+from.x, (towards.y)/center+from.y, (towards.z)/center+from.z, particles/divider, + towards.x/dev, towards.y/dev, towards.z/dev, 0.0); + world.spawnParticles(player, particle, true, + (towards.x)*(1.0-1.0/center)+from.x, (towards.y)*(1.0-1.0/center)+from.y, (towards.z)*(1.0-1.0/center)+from.z, particles/divider, + towards.x/dev, towards.y/dev, towards.z/dev, 0.0); + } + parts += 2*particles/divider; + divider = 2*divider; + } + return parts; + } + + public static int drawParticleLine(List players, ParticleEffect particle, Vec3d from, Vec3d to, double density) + { + if (isStraight(from, to, density)) return drawOptimizedParticleLine(players, particle, from, to, density); + double lineLengthSq = from.squaredDistanceTo(to); + if (lineLengthSq == 0) return 0; + Vec3d incvec = to.subtract(from).multiply(2*density/sqrt(lineLengthSq)); + int pcount = 0; + for (Vec3d delta = new Vec3d(0.0,0.0,0.0); + delta.lengthSquared() shapes; + private final Map>> shapes; + private MinecraftClient client; - void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, double cameraX, double cameraY, double cameraZ){ + private Map>> renderedShapes + = new HashMap>>() + {{ + put("debugBox", RenderedBox::new); + put("debugLine", RenderedLine::new); + }}; - } - void addShape() {} - - - public abstract static class ExpiringShape + public ShapesRenderer(MinecraftClient minecraftClient) { - protected int r; - protected int g; - protected int b; - protected int a; - protected int expiryTick = 0; - public abstract void render(MatrixStack matrix, VertexConsumer vertexConsumer); - public ExpiringShape expiresIn(int ticks) + this.client = minecraftClient; + shapes = new HashMap<>(); + shapes.put(DimensionType.OVERWORLD, new ArrayList<>()); + shapes.put(DimensionType.THE_NETHER, new ArrayList<>()); + shapes.put(DimensionType.THE_END, new ArrayList<>()); + } + + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, double cameraX, double cameraY, double cameraZ) + { + //Camera camera = this.client.gameRenderer.getCamera(); + IWorld iWorld = this.client.world; + DimensionType dimensionType = iWorld.getDimension().getType(); + if (shapes.get(dimensionType).isEmpty()) return; + //BlockPos blockPos = new BlockPos(camera.getPos().x, 0.0D, camera.getPos().z); + VertexConsumer vertexConsumer = vertexConsumers.getBuffer(RenderLayer.getLines()); + long currentTime = client.world.getTime(); + + synchronized (shapes) { - expiryTick = ticks; - return this; + Iterator> it = shapes.get(dimensionType).iterator(); + while (it.hasNext()) + { + RenderedShape shape = it.next(); + if (shape.isExpired(currentTime)) it.remove(); + shape.render(matrices, vertexConsumer, (float) cameraX, (float) cameraY, (float) cameraZ); + } + } + } + public void addShape(String type, DimensionType dim, CompoundTag tag) + { + BiFunction> shapeFactory; + shapeFactory = renderedShapes.get(type); + if (shapeFactory == null) + { + CarpetSettings.LOG.error("Unrecognized shape: "+type); + } + else + { + synchronized (shapes) { shapes.get(dim).add(shapeFactory.apply(client, tag)); } + } + } + public void reset() + { + synchronized (shapes) + { + shapes.values().forEach(List::clear); + } + } + + + public abstract static class RenderedShape + { + T shape; + long expiryTick; + public abstract void render(MatrixStack matrices, VertexConsumer vertexConsumer, float cx, float cy, float cz ); + protected RenderedShape(MinecraftClient client, T shape) + { + this.shape = shape; + expiryTick = client.world.getTime()+shape.getExpiry(); } + public boolean isExpired(long currentTick) + { + return expiryTick < currentTick; + } } - public static class SolidLine extends ExpiringShape + public static class RenderedBox extends RenderedShape { - public SolidLine(Vec3d from, Vec3d to, int r, int g, int b, int a) + public RenderedBox(MinecraftClient client, CompoundTag boxData) { - this.r = r; - this.g = g; - this.b = b; - this.a = a; - + super(client, (ShapeDispatcher.Box)ShapeDispatcher.Box.fromTag(boxData)); } @Override - public void render(MatrixStack matrix, VertexConsumer vertexConsumer) + public void render(MatrixStack matrices, VertexConsumer vertexConsumer, float cx, float cy, float cz) { - + drawBox(matrices, vertexConsumer, + shape.x1-cx, shape.y1-cy, shape.z1-cz, + shape.x2-cx, shape.y2-cy, shape.z2-cz, + shape.r, shape.g, shape.b, shape.a, shape.r, shape.g, shape.b + ); } } + public static class RenderedLine extends RenderedShape + { + public RenderedLine(MinecraftClient client, CompoundTag boxData) + { + super(client, (ShapeDispatcher.Line)ShapeDispatcher.Line.fromTag(boxData)); + } + @Override + public void render(MatrixStack matrices, VertexConsumer vertexConsumer, float cx, float cy, float cz) + { + drawLine(matrices, vertexConsumer, + shape.x1-cx, shape.y1-cy, shape.z1-cz, + shape.x2-cx, shape.y2-cy, shape.z2-cz, + shape.r, shape.g, shape.b, shape.a + ); + } + } + + // some raw shit + + public static void drawLine(MatrixStack matrix, VertexConsumer vertexConsumer, float x1, float y1, float z1, float x2, float y2, float z2, float red1, float grn1, float blu1, float alpha) { + Matrix4f matrix4f = matrix.peek().getModel(); + vertexConsumer.vertex(matrix4f, x1, y1, z1).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y2, z2).color(red1, grn1, blu1, alpha).next(); + } + + public static void drawBox(MatrixStack matrix, VertexConsumer vertexConsumer, float x1, float y1, float z1, float x2, float y2, float z2, float red1, float grn1, float blu1, float alpha, float red2, float grn2, float blu2) { + Matrix4f matrix4f = matrix.peek().getModel(); + vertexConsumer.vertex(matrix4f, x1, y1, z1).color(red1, grn2, blu2, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y1, z1).color(red1, grn2, blu2, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y1, z1).color(red2, grn1, blu2, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y2, z1).color(red2, grn1, blu2, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y1, z1).color(red2, grn2, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y1, z2).color(red2, grn2, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y1, z1).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y2, z1).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y2, z1).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y2, z1).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y2, z1).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y2, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y2, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y1, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y1, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y1, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y1, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y1, z1).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x1, y2, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y2, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y1, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y2, z2).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y2, z1).color(red1, grn1, blu1, alpha).next(); + vertexConsumer.vertex(matrix4f, x2, y2, z2).color(red1, grn1, blu1, alpha).next(); + } } diff --git a/src/main/resources/assets/carpet/scripts/overlay.sc b/src/main/resources/assets/carpet/scripts/overlay.sc index a2909b04..45be90a4 100644 --- a/src/main/resources/assets/carpet/scripts/overlay.sc +++ b/src/main/resources/assets/carpet/scripts/overlay.sc @@ -94,17 +94,24 @@ __structure_renderer(player_name) -> l(from, to) = structure_data; total_size = _euclidean(from, to); density = max(10, total_size/10); - particle_rect('dust 0 1 1 2', from, to+1, density); + //particle_box('dust 0 1 1 2', from, to+1, density); + marker_box(from, to+1, 10, 0x00FFFFFF ); structure_pieces = slice(sort_key(structure_pieces, _euclidean_sq((_:2+_:3)/2,ppos)), 0, config:'max_pieces'); for (structure_pieces, l(piece, direction, from, to) = _; - factor = 1- _i / config:'max_pieces' / 2; - total_size = _euclidean(from, to); - density = max(_i+1, total_size/10); - particle_rect(str('dust %.2f %.2f 1 1',factor, factor), from, to+1, density); + //factor = 1- _i / config:'max_pieces' / 2; + //total_size = _euclidean(from, to); + //density = max(_i+1, total_size/10); + //particle_box(str('dust %.2f %.2f 1 1',factor, factor), from, to+1, density); + r = 255 - 128 * _i/config:'max_pieces'; + g = r; + b = 255; + a = 255; + color = a+256*(b+256*(g+256*r)); + marker_box(from, to+1, 10, color); ) ) ); - schedule(3, '__structure_renderer', player_name); + schedule(5, '__structure_renderer', player_name); ); __chunk_renderer(player_name) -> @@ -128,15 +135,27 @@ __chunk_renderer(player_name) -> top_01 = ref_pos + l(0, top('terrain', ref_pos+l(0, 0, 15))+10, 16); part = 'dust 0.2 0.8 0.2 2'; density = 2+player_distance/2; - particle_line(part, top_00, top_10, density); - particle_line(part, top_10, top_11, density); - particle_line(part, top_11, top_01, density); - particle_line(part, top_01, top_00, density); - particle_line(part, top_00, top_11, density); - particle_line(part, top_01, top_10, density); + r = 30; + g = 220; + b = 30; + a = max(0, 255-player_distance); + color = a+256*(b+256*(g+256*r)); + marker_line(top_00, top_10, 10, color); + marker_line(top_10, top_11, 10, color); + marker_line(top_11, top_01, 10, color); + marker_line(top_01, top_00, 10, color); + marker_line(top_00, top_11, 10, color); + marker_line(top_01, top_10, 10, color); + + //particle_line(part, top_00, top_10, density); + //particle_line(part, top_10, top_11, density); + //particle_line(part, top_11, top_01, density); + //particle_line(part, top_01, top_00, density); + //particle_line(part, top_00, top_11, density); + //particle_line(part, top_01, top_10, density); ) ) ) ); - schedule(3, '__chunk_renderer', player_name); + schedule(5, '__chunk_renderer', player_name); ) \ No newline at end of file diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 247b24d1..4b63eb8e 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -181,8 +181,8 @@ "ClientPlayerEntity_clientCommandMixin", "WorldRenderer_fogOffMixin", "WorldRenderer_creativeNoClipMixin", - "ClientPlayNetworkHandlerMixin" - + "ClientPlayNetworkHandlerMixin", + "DebugRenderer_scarpetRenderMixin" ], "server": [ "MinecraftDedicatedServerMixin"