diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3437112b3..6888903e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-java@v2 with: distribution: temurin - java-version: 17 + java-version: 21 - uses: actions/setup-node@v3 with: node-version: 16 diff --git a/.github/workflows/build_pr.yml b/.github/workflows/build_pr.yml index e8af231e7..2f2b7be1f 100644 --- a/.github/workflows/build_pr.yml +++ b/.github/workflows/build_pr.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [17] + java: [21] fail-fast: true steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 8794b0ec8..c2600239a 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/setup-java@v2 with: distribution: temurin - java-version: 17 + java-version: 21 - uses: actions/setup-node@v3 with: node-version: 16 diff --git a/README.md b/README.md index 2bfc743e4..ae471f209 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ Maven maven.modrinth pl3xmap - 1.20.4-476 + 1.20.6-493 provided ``` @@ -101,7 +101,7 @@ repositories { } dependencies { - compileOnly 'maven.modrinth:pl3xmap:1.20.4-476' + compileOnly 'maven.modrinth:pl3xmap:1.20.6-493' } ``` diff --git a/build.gradle b/build.gradle index d3e4bd5c8..e95efab14 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,7 @@ def combineJars = tasks.register('combineJars', Jar) { def copyWebmap = tasks.register('copyWebmap', Copy) { dependsOn ':webmap:buildWebmap' - duplicatesStrategy = DuplicatesStrategy.INHERIT + duplicatesStrategy = DuplicatesStrategy.EXCLUDE from "$rootDir/webmap/public", "$rootDir/webmap/dist" include '**/*' exclude 'tiles*/' @@ -70,21 +70,39 @@ allprojects { java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } } repositories { mavenCentral() - mavenLocal() - maven { url = 'https://jitpack.io' } + maven { + url = 'https://repo.granny.dev/snapshots/' + } + maven { + name = 'sonatype-snapshots' + url = 'https://oss.sonatype.org/content/repositories/snapshots/' + mavenContent { + snapshotsOnly() + } + } + maven { + url = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' + mavenContent { + snapshotsOnly() + } + } + maven { + url = 'https://jitpack.io' + } } dependencies { - implementation "cloud.commandframework:cloud-core:$cloudVersion" - implementation "cloud.commandframework:cloud-brigadier:$cloudVersion" - implementation "cloud.commandframework:cloud-paper:$cloudVersion" - implementation("cloud.commandframework:cloud-minecraft-extras:$cloudVersion") { + implementation "org.incendo:cloud-core:$cloudVersion" + implementation "org.incendo:cloud-brigadier:$cloudVersion" + implementation "org.incendo:cloud-paper:$cloudVersion" + implementation "org.incendo:cloud-processors-confirmation:1.0.0-SNAPSHOT" + implementation("org.incendo:cloud-minecraft-extras:$cloudVersion") { exclude group: 'net.kyori', module: '*' } @@ -120,7 +138,7 @@ allprojects { compileJava { options.encoding = 'UTF-8' - options.release.set(17) + options.release.set(21) } processResources { diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitNetwork.java b/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitNetwork.java index 6a04f2da3..2867f6631 100644 --- a/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitNetwork.java +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitNetwork.java @@ -23,16 +23,22 @@ */ package net.pl3x.map.bukkit; -import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataOutput; +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.pl3x.map.bukkit.network.ClientboundMapPayload; +import net.pl3x.map.bukkit.network.ClientboundServerPayload; +import net.pl3x.map.bukkit.network.ServerboundMapPayload; +import net.pl3x.map.bukkit.network.ServerboundServerPayload; +import net.pl3x.map.core.configuration.Config; import net.pl3x.map.core.network.Constants; import net.pl3x.map.core.network.Network; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R3.map.CraftMapRenderer; import org.bukkit.entity.Player; -import org.bukkit.map.MapRenderer; import org.bukkit.map.MapView; +import org.jetbrains.annotations.NotNull; public class BukkitNetwork extends Network { private final Pl3xMapBukkit plugin; @@ -42,69 +48,77 @@ public BukkitNetwork(Pl3xMapBukkit plugin) { } public void register() { - Bukkit.getMessenger().registerOutgoingPluginChannel(this.plugin, Network.CHANNEL); - Bukkit.getMessenger().registerIncomingPluginChannel(this.plugin, Network.CHANNEL, + Bukkit.getMessenger().registerOutgoingPluginChannel(this.plugin, ClientboundServerPayload.TYPE.id().toString()); + Bukkit.getMessenger().registerOutgoingPluginChannel(this.plugin, ClientboundMapPayload.TYPE.id().toString()); + Bukkit.getMessenger().registerIncomingPluginChannel(this.plugin, ServerboundServerPayload.TYPE.id().toString(), (channel, player, bytes) -> { - ByteArrayDataInput in = in(bytes); - int protocol = in.readInt(); - if (protocol != Constants.PROTOCOL) { + ClientboundServerPayload payload = new ClientboundServerPayload(Constants.PROTOCOL, Constants.RESPONSE_SUCCESS, Config.WEB_ADDRESS); + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + ClientboundServerPayload.STREAM_CODEC.encode(friendlyByteBuf, payload); + sendCustomPayloadPacket(player, payload, friendlyByteBuf); + } + ); + Bukkit.getMessenger().registerIncomingPluginChannel(this.plugin, ServerboundMapPayload.TYPE.id().toString(), + (channel, player, bytes) -> { + FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.copiedBuffer(bytes)); + ServerboundMapPayload payload = ServerboundMapPayload.STREAM_CODEC.decode(byteBuf); + + MapView map = Bukkit.getMap(payload.mapId()); + if (map == null) { + ClientboundMapPayload customPacketPayload = new ClientboundMapPayload(Constants.PROTOCOL, Constants.ERROR_NO_SUCH_MAP, payload.mapId()); + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer(bytes.length)); + ClientboundMapPayload.STREAM_CODEC.encode(friendlyByteBuf, customPacketPayload); + sendCustomPayloadPacket(player, customPacketPayload, friendlyByteBuf); return; } - int action = in.readInt(); - switch (action) { - case Constants.SERVER_DATA -> sendServerData(player); - case Constants.MAP_DATA -> sendMapData(player, in.readInt()); + + World world = map.getWorld(); + if (world == null) { + ClientboundMapPayload customPacketPayload = new ClientboundMapPayload(Constants.PROTOCOL, Constants.ERROR_NO_SUCH_WORLD, payload.mapId()); + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + ClientboundMapPayload.STREAM_CODEC.encode(friendlyByteBuf, customPacketPayload); + sendCustomPayloadPacket(player, customPacketPayload, friendlyByteBuf); + return; } + + ClientboundMapPayload customPacketPayload = new ClientboundMapPayload( + Constants.PROTOCOL, Constants.RESPONSE_SUCCESS, payload.mapId(), + getScale(map), map.getCenterX(), map.getCenterZ(), world.getName() + ); + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + ClientboundMapPayload.STREAM_CODEC.encode(friendlyByteBuf, customPacketPayload); + sendCustomPayloadPacket(player, customPacketPayload, friendlyByteBuf); } ); } + @NotNull + private void sendCustomPayloadPacket(Player player, CustomPacketPayload customPacketPayload, FriendlyByteBuf friendlyByteBuf) { + byte[] byteArray = new byte[friendlyByteBuf.readableBytes()]; + friendlyByteBuf.readBytes(byteArray); + player.sendPluginMessage(this.plugin, customPacketPayload.type().id().toString(), byteArray); + } + public void unregister() { - Bukkit.getMessenger().unregisterOutgoingPluginChannel(this.plugin, Network.CHANNEL); - Bukkit.getMessenger().unregisterIncomingPluginChannel(this.plugin, Network.CHANNEL); + Bukkit.getMessenger().unregisterOutgoingPluginChannel(this.plugin, ClientboundServerPayload.TYPE.id().toString()); + Bukkit.getMessenger().unregisterOutgoingPluginChannel(this.plugin, ClientboundMapPayload.TYPE.id().toString()); + Bukkit.getMessenger().unregisterIncomingPluginChannel(this.plugin, ServerboundServerPayload.TYPE.id().toString()); + Bukkit.getMessenger().unregisterIncomingPluginChannel(this.plugin, ServerboundMapPayload.TYPE.id().toString()); + } + + @Override + protected void sendServerData(T player) { + } + @Override protected void sendMapData(T player, int id) { - ByteArrayDataOutput out = out(); - - out.writeInt(Constants.PROTOCOL); - out.writeInt(Constants.MAP_DATA); - out.writeInt(Constants.RESPONSE_SUCCESS); - - MapView map = Bukkit.getMap(id); - if (map == null) { - out.writeInt(Constants.ERROR_NO_SUCH_MAP); - out.writeInt(id); - return; - } - - World world = map.getWorld(); - if (world == null) { - out.writeInt(Constants.ERROR_NO_SUCH_WORLD); - out.writeInt(id); - return; - } - - for (MapRenderer renderer : map.getRenderers()) { - if (!renderer.getClass().getName().equals(CraftMapRenderer.class.getName())) { - out.writeInt(Constants.ERROR_NOT_VANILLA_MAP); - out.writeInt(id); - return; - } - } - - out.writeInt(id); - out.writeByte(getScale(map)); - out.writeInt(map.getCenterX()); - out.writeInt(map.getCenterZ()); - out.writeUTF(world.getName()); - - send(player, out); + } @Override protected void send(T player, ByteArrayDataOutput out) { - ((Player) player).sendPluginMessage(this.plugin, Network.CHANNEL, out.toByteArray()); + } @SuppressWarnings("deprecation") diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitPlayer.java b/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitPlayer.java index c1af9ed22..190251e28 100644 --- a/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitPlayer.java +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitPlayer.java @@ -42,8 +42,8 @@ import org.bukkit.NamespacedKey; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitWorld.java b/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitWorld.java index f09a27da4..49faae157 100644 --- a/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitWorld.java +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/BukkitWorld.java @@ -52,7 +52,7 @@ public BukkitWorld(@NotNull ServerLevel level, @NotNull String name) { super( name, level.getSeed(), - Point.of(level.getLevelData().getXSpawn(), level.getLevelData().getZSpawn()), + Point.of(level.getLevelData().getSpawnPos().getX(), level.getLevelData().getSpawnPos().getZ()), Type.get(level.dimension().location().toString()), level.convertable.getDimensionPath(level.dimension()).resolve("region") ); diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/Pl3xMapBukkit.java b/bukkit/src/main/java/net/pl3x/map/bukkit/Pl3xMapBukkit.java index cd91e7dc0..1837efa5d 100644 --- a/bukkit/src/main/java/net/pl3x/map/bukkit/Pl3xMapBukkit.java +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/Pl3xMapBukkit.java @@ -32,7 +32,7 @@ import net.pl3x.map.core.player.PlayerListener; import net.pl3x.map.core.player.PlayerRegistry; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -81,7 +81,7 @@ public void onEnable() { } getServer().getScheduler().runTaskTimer(this, () -> - this.pl3xmap.getScheduler().tick(), 20, 20); + this.pl3xmap.getScheduler().tick(), 20, 1); } @Override diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/Pl3xMapImpl.java b/bukkit/src/main/java/net/pl3x/map/bukkit/Pl3xMapImpl.java index 3fc957178..7fda1684b 100644 --- a/bukkit/src/main/java/net/pl3x/map/bukkit/Pl3xMapImpl.java +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/Pl3xMapImpl.java @@ -52,7 +52,7 @@ import net.pl3x.map.core.registry.BlockRegistry; import net.pl3x.map.core.world.World; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/command/BukkitCommandManager.java b/bukkit/src/main/java/net/pl3x/map/bukkit/command/BukkitCommandManager.java index 3432bc471..d69582688 100644 --- a/bukkit/src/main/java/net/pl3x/map/bukkit/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/command/BukkitCommandManager.java @@ -23,14 +23,16 @@ */ package net.pl3x.map.bukkit.command; -import cloud.commandframework.Command; -import cloud.commandframework.brigadier.CloudBrigadierManager; -import cloud.commandframework.bukkit.CloudBukkitCapabilities; -import cloud.commandframework.execution.CommandExecutionCoordinator; -import cloud.commandframework.paper.PaperCommandManager; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Sender; +import net.pl3x.map.core.command.parser.PlatformParsers; import org.bukkit.plugin.Plugin; +import org.incendo.cloud.Command; +import org.incendo.cloud.SenderMapper; +import org.incendo.cloud.brigadier.CloudBrigadierManager; +import org.incendo.cloud.bukkit.CloudBukkitCapabilities; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.paper.PaperCommandManager; import org.jetbrains.annotations.NotNull; public class BukkitCommandManager implements CommandHandler { @@ -38,7 +40,9 @@ public class BukkitCommandManager implements CommandHandler { private final Command.Builder<@NotNull Sender> root; public BukkitCommandManager(@NotNull Plugin plugin) throws Exception { - this.manager = new PaperCommandManager<>(plugin, CommandExecutionCoordinator.simpleCoordinator(), BukkitSender::create, Sender::getSender); + this.manager = new PaperCommandManager(plugin, + ExecutionCoordinator.simpleCoordinator(), + SenderMapper.create(BukkitSender::create, Sender::getSender)); if (getManager().hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) { getManager().registerBrigadier(); @@ -64,6 +68,11 @@ public BukkitCommandManager(@NotNull Plugin plugin) throws Exception { return this.manager; } + @Override + public @NotNull PlatformParsers getPlatformParsers() { + return new BukkitParsers(); + } + @Override public Command.@NotNull Builder<@NotNull Sender> getRoot() { return this.root; diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/command/BukkitParsers.java b/bukkit/src/main/java/net/pl3x/map/bukkit/command/BukkitParsers.java new file mode 100644 index 000000000..772506fa3 --- /dev/null +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/command/BukkitParsers.java @@ -0,0 +1,52 @@ +package net.pl3x.map.bukkit.command; + +import net.minecraft.world.phys.Vec3; +import net.pl3x.map.bukkit.BukkitPlayer; +import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.command.Sender; +import net.pl3x.map.core.command.parser.PlatformParsers; +import net.pl3x.map.core.markers.Point; +import net.pl3x.map.core.player.Player; +import org.incendo.cloud.bukkit.data.SinglePlayerSelector; +import org.incendo.cloud.bukkit.parser.location.Location2D; +import org.incendo.cloud.bukkit.parser.location.Location2DParser; +import org.incendo.cloud.bukkit.parser.selector.SinglePlayerSelectorParser; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.ParserDescriptor; + +public class BukkitParsers implements PlatformParsers { + @Override + public ParserDescriptor columnPosParser() { + return Location2DParser.location2DParser(); + } + + @Override + public Point resolvePointFromColumnPos(String name, CommandContext context) { + Location2D location2D = context.getOrDefault(name, null); + if (location2D == null) { + return Point.ZERO; + } + return Point.of(location2D.blockX(), location2D.blockZ()); + } + + @Override + public ParserDescriptor playerSelectorParser() { + return SinglePlayerSelectorParser.singlePlayerSelectorParser(); + } + + @Override + public Player resolvePlayerFromPlayerSelector(String name, CommandContext context) { + Sender sender = context.sender(); + SinglePlayerSelector playerSelector = context.getOrDefault(name, null); + if (playerSelector == null) { + if (sender instanceof Sender.Player senderPlayer) { + Player player = Pl3xMap.api().getPlayerRegistry().get(senderPlayer.getUUID()); + if (player != null) { + return player; + } + } + return null; + } + return new BukkitPlayer(playerSelector.single()); + } +} diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/network/ClientboundMapPayload.java b/bukkit/src/main/java/net/pl3x/map/bukkit/network/ClientboundMapPayload.java new file mode 100644 index 000000000..cd72c754c --- /dev/null +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/network/ClientboundMapPayload.java @@ -0,0 +1,35 @@ +package net.pl3x.map.bukkit.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.pl3x.map.core.network.Constants; + +public record ClientboundMapPayload(int protocol, int response, int mapId, byte scale, int centerX, int centerZ, String worldName) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundMapPayload::write, ClientboundMapPayload::new); + public static final Type TYPE = new Type<>(new ResourceLocation(Constants.MODID, "client_map_data")); + + public ClientboundMapPayload(int protocol, int response, int mapId) { + this(protocol, response, mapId, (byte) 0, 0, 0, null); + } + + public ClientboundMapPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readInt(), friendlyByteBuf.readInt(), friendlyByteBuf.readInt(), friendlyByteBuf.readByte(), friendlyByteBuf.readInt(), friendlyByteBuf.readInt(), friendlyByteBuf.readUtf()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeInt(protocol); + friendlyByteBuf.writeInt(response); + friendlyByteBuf.writeInt(mapId); + friendlyByteBuf.writeByte(scale); + friendlyByteBuf.writeInt(centerX); + friendlyByteBuf.writeInt(centerZ); + friendlyByteBuf.writeUtf(worldName); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/network/ClientboundServerPayload.java b/bukkit/src/main/java/net/pl3x/map/bukkit/network/ClientboundServerPayload.java new file mode 100644 index 000000000..bcc4df553 --- /dev/null +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/network/ClientboundServerPayload.java @@ -0,0 +1,31 @@ +package net.pl3x.map.bukkit.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.pl3x.map.core.network.Constants; + +public record ClientboundServerPayload(int protocol, int response, String webAddress) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundServerPayload::write, ClientboundServerPayload::new); + public static final Type TYPE = new Type<>(new ResourceLocation(Constants.MODID, "client_server_data")); + + public ClientboundServerPayload(int protocol, int response) { + this(protocol, response, null); + } + + public ClientboundServerPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readInt(), friendlyByteBuf.readInt(), friendlyByteBuf.readUtf()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeInt(protocol); + friendlyByteBuf.writeInt(response); + friendlyByteBuf.writeUtf(webAddress); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/network/ServerboundMapPayload.java b/bukkit/src/main/java/net/pl3x/map/bukkit/network/ServerboundMapPayload.java new file mode 100644 index 000000000..1b5d87511 --- /dev/null +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/network/ServerboundMapPayload.java @@ -0,0 +1,30 @@ +package net.pl3x.map.bukkit.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.pl3x.map.core.network.Constants; + +public record ServerboundMapPayload(int protocol, int mapId) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundMapPayload::write, ServerboundMapPayload::new); + public static final Type TYPE = new Type<>(new ResourceLocation(Constants.MODID, "server_map_data")); + + public ServerboundMapPayload(int mapId) { + this(Constants.PROTOCOL, mapId); + } + + public ServerboundMapPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readInt(), friendlyByteBuf.readInt()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeInt(protocol); + friendlyByteBuf.writeInt(mapId); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/bukkit/src/main/java/net/pl3x/map/bukkit/network/ServerboundServerPayload.java b/bukkit/src/main/java/net/pl3x/map/bukkit/network/ServerboundServerPayload.java new file mode 100644 index 000000000..0601dd640 --- /dev/null +++ b/bukkit/src/main/java/net/pl3x/map/bukkit/network/ServerboundServerPayload.java @@ -0,0 +1,25 @@ +package net.pl3x.map.bukkit.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.pl3x.map.core.network.Constants; + +public record ServerboundServerPayload(int protocol) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundServerPayload::write, ServerboundServerPayload::new); + public static final Type TYPE = new Type<>(new ResourceLocation(Constants.MODID, "server_server_data")); + + public ServerboundServerPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readInt()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeInt(protocol); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/pl3x/map/core/command/CommandHandler.java b/core/src/main/java/net/pl3x/map/core/command/CommandHandler.java index bab0efe0b..ef38bfdb8 100644 --- a/core/src/main/java/net/pl3x/map/core/command/CommandHandler.java +++ b/core/src/main/java/net/pl3x/map/core/command/CommandHandler.java @@ -23,15 +23,11 @@ */ package net.pl3x.map.core.command; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; -import cloud.commandframework.meta.CommandMeta; -import cloud.commandframework.minecraft.extras.AudienceProvider; -import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler; import java.util.List; import java.util.function.UnaryOperator; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; +import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.command.commands.ConfirmCommand; import net.pl3x.map.core.command.commands.FullRenderCommand; import net.pl3x.map.core.command.commands.HelpCommand; @@ -45,8 +41,15 @@ import net.pl3x.map.core.command.commands.StatusCommand; import net.pl3x.map.core.command.commands.StitchCommand; import net.pl3x.map.core.command.commands.VersionCommand; +import net.pl3x.map.core.command.parser.PlatformParsers; import net.pl3x.map.core.configuration.Config; import net.pl3x.map.core.configuration.Lang; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.description.CommandDescription; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.minecraft.extras.AudienceProvider; +import org.incendo.cloud.minecraft.extras.MinecraftExceptionHandler; import org.jetbrains.annotations.NotNull; /** @@ -60,6 +63,13 @@ public interface CommandHandler { */ @NotNull CommandManager<@NotNull Sender> getManager(); + /** + * Get the platform parsers. + * + * @return platform parsers + */ + @NotNull PlatformParsers getPlatformParsers(); + /** * Get the root command. * @@ -68,15 +78,15 @@ public interface CommandHandler { Command.@NotNull Builder<@NotNull Sender> getRoot(); default void setupExceptionHandlers() { - new MinecraftExceptionHandler() - .withDefaultHandlers() - .withDecorator(component -> Component.text() + MinecraftExceptionHandler.createNative() + .defaultHandlers() + .decorator(component -> Component.text() .append(Lang.parse(Lang.PREFIX_COMMAND) .hoverEvent(Lang.parse(Lang.CLICK_FOR_HELP)) .clickEvent(ClickEvent.runCommand("/map help"))) .append(component) .build()) - .apply(getManager(), AudienceProvider.nativeAudience()); + .registerTo(getManager()); } /** @@ -91,9 +101,9 @@ default void registerSubcommand(@NotNull UnaryOperator buildRoot() { return getManager().commandBuilder("map", "pl3xmap") .permission("pl3xmap.command.map") - .meta(CommandMeta.DESCRIPTION, "Pl3xMap command. '/map help'") + .commandDescription(CommandDescription.commandDescription("Pl3xMap command. '/map help'")) .handler(context -> { - context.getSender().sendMessage(Lang.COMMAND_BASE + context.sender().sendMessage(Lang.COMMAND_BASE // minimessage doesn't seem to handle placeholders inside // placeholders, so we have to replace this one manually .replace("", Config.WEB_ADDRESS)); diff --git a/core/src/main/java/net/pl3x/map/core/command/Pl3xMapCommand.java b/core/src/main/java/net/pl3x/map/core/command/Pl3xMapCommand.java index 8eadf82cd..0ceba395c 100644 --- a/core/src/main/java/net/pl3x/map/core/command/Pl3xMapCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/Pl3xMapCommand.java @@ -23,9 +23,9 @@ */ package net.pl3x.map.core.command; -import cloud.commandframework.minecraft.extras.RichDescription; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.pl3x.map.core.configuration.Lang; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; /** diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/PlayerArgument.java b/core/src/main/java/net/pl3x/map/core/command/argument/PlayerArgument.java deleted file mode 100644 index 49881dd1d..000000000 --- a/core/src/main/java/net/pl3x/map/core/command/argument/PlayerArgument.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-2023 William Blake Galbreath - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package net.pl3x.map.core.command.argument; - -import cloud.commandframework.ArgumentDescription; -import cloud.commandframework.arguments.CommandArgument; -import cloud.commandframework.context.CommandContext; -import java.util.List; -import java.util.function.BiFunction; -import net.pl3x.map.core.Pl3xMap; -import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.parser.PlayerParser; -import net.pl3x.map.core.command.exception.PlayerParseException; -import net.pl3x.map.core.configuration.Lang; -import net.pl3x.map.core.player.Player; -import org.jetbrains.annotations.NotNull; - -/** - * A {@link Player} argument that belongs to a command. - * - * @param command sender type - */ -public class PlayerArgument extends CommandArgument<@NotNull C, @NotNull Player> { - protected PlayerArgument(boolean required, @NotNull String name, @NotNull String defaultValue, @NotNull BiFunction<@NotNull CommandContext<@NotNull C>, @NotNull String, @NotNull List<@NotNull String>> suggestionsProvider, @NotNull ArgumentDescription defaultDescription) { - super(required, name, new PlayerParser<>(), defaultValue, Player.class, suggestionsProvider, defaultDescription); - } - - /** - * Create a new {@link PlayerArgument} builder. - * - * @param name argument name - * @return new player argument builder - */ - public static CommandArgument.@NotNull Builder<@NotNull C, @NotNull Player> newBuilder(@NotNull String name) { - return new Builder<>(name); - } - - /** - * Create a required {@link PlayerArgument}. - * - * @param name argument name - * @return constructed player argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull Player> of(@NotNull String name) { - return PlayerArgument.<@NotNull C>newBuilder(name).asRequired().build(); - } - - /** - * Create an optional {@link PlayerArgument}. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @return constructed player argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull Player> optional(@NotNull String name) { - return PlayerArgument.<@NotNull C>newBuilder(name).asOptional().build(); - } - - /** - * Create an optional {@link PlayerArgument} with a default value. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @param defaultValue default value that will be used if none was supplied - * @return constructed player argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull Player> optional(@NotNull String name, @NotNull String defaultValue) { - return PlayerArgument.<@NotNull C>newBuilder(name).asOptionalWithDefault(defaultValue).build(); - } - - /** - * Resolve {@link Player} from command context. - *

- * If context does not contain a {@link Player} and the sender is a {@link net.pl3x.map.core.command.Sender.Player} then the sender will be used. - * - * @param context command context - * @param name argument name - * @return player - * @throws PlayerParseException if context did not contain a {@link Player} and the sender is not a {@link net.pl3x.map.core.command.Sender.Player} - */ - public static @NotNull Player resolve(@NotNull CommandContext<@NotNull Sender> context, @NotNull String name) { - Sender sender = context.getSender(); - Player player = context.getOrDefault(name, null); - if (player != null) { - return player; - } - if (sender instanceof Sender.Player senderPlayer) { - player = Pl3xMap.api().getPlayerRegistry().get(senderPlayer.getUUID()); - if (player != null) { - return player; - } - } - sender.sendMessage(Lang.ERROR_MUST_SPECIFY_PLAYER); - throw new PlayerParseException(null, PlayerParseException.MUST_SPECIFY_PLAYER); - } - - /** - * Mutable builder for {@link PlayerArgument} instances. - * - * @param command sender type - */ - public static class Builder extends CommandArgument.Builder<@NotNull C, @NotNull Player> { - private Builder(@NotNull String name) { - super(Player.class, name); - } - - @Override - public @NotNull CommandArgument<@NotNull C, @NotNull Player> build() { - return new PlayerArgument<>(isRequired(), getName(), getDefaultValue(), getSuggestionsProvider(), getDefaultDescription()); - } - } -} diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/PointArgument.java b/core/src/main/java/net/pl3x/map/core/command/argument/PointArgument.java deleted file mode 100644 index 7de2ffca1..000000000 --- a/core/src/main/java/net/pl3x/map/core/command/argument/PointArgument.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-2023 William Blake Galbreath - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package net.pl3x.map.core.command.argument; - -import cloud.commandframework.ArgumentDescription; -import cloud.commandframework.arguments.CommandArgument; -import cloud.commandframework.context.CommandContext; -import java.util.List; -import java.util.function.BiFunction; -import net.pl3x.map.core.Pl3xMap; -import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.parser.PointParser; -import net.pl3x.map.core.markers.Point; -import net.pl3x.map.core.player.Player; -import org.jetbrains.annotations.NotNull; - -/** - * A {@link Point} argument that belongs to a command. - * - * @param command sender type - */ -@SuppressWarnings("unused") -public class PointArgument extends CommandArgument<@NotNull C, @NotNull Point> { - protected PointArgument(boolean required, @NotNull String name, @NotNull String defaultValue, @NotNull BiFunction<@NotNull CommandContext<@NotNull C>, @NotNull String, @NotNull List<@NotNull String>> suggestionsProvider, @NotNull ArgumentDescription defaultDescription) { - super(required, name, new PointParser<>(), defaultValue, Point.class, suggestionsProvider, defaultDescription); - } - - /** - * Create a new {@link PointArgument} builder. - * - * @param name argument name - * @return new point argument builder - */ - public static CommandArgument.@NotNull Builder<@NotNull C, @NotNull Point> builder(@NotNull String name) { - return new PointArgument.Builder<>(name); - } - - /** - * Create a required {@link PointArgument}. - * - * @param name argument name - * @return constructed point argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull Point> of(@NotNull String name) { - return PointArgument.<@NotNull C>builder(name).asRequired().build(); - } - - /** - * Create an optional {@link PointArgument}. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @return constructed point argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull Point> optional(@NotNull String name) { - return PointArgument.<@NotNull C>builder(name).asOptional().build(); - } - - /** - * Create an optional {@link PointArgument} with a default value. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @param defaultValue default value that will be used if none was supplied - * @return constructed point argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull Point> optional(@NotNull String name, @NotNull String defaultValue) { - return PointArgument.<@NotNull C>builder(name).asOptionalWithDefault(defaultValue).build(); - } - - /** - * Resolve {@link Point} from command context. - *

- * If context does not contain a {@link Point} and the sender is a {@link net.pl3x.map.core.command.Sender.Player} then the sender's location will be used, otherwise [0, 0] will be used - * - * @param context command context - * @param name argument name - * @return player - */ - public static @NotNull Point resolve(@NotNull CommandContext<@NotNull Sender> context, @NotNull String name) { - Sender sender = context.getSender(); - Point point = context.getOrDefault(name, null); - if (point != null) { - return point; - } - if (sender instanceof Sender.Player) { - Player player = Pl3xMap.api().getPlayerRegistry().get(((Sender.Player) sender).getUUID()); - point = player == null ? Point.ZERO : player.getPosition(); - } else { - point = Point.ZERO; - } - return point; - } - - /** - * Mutable builder for {@link PointArgument} instances. - * - * @param command sender type - */ - public static class Builder extends CommandArgument.Builder<@NotNull C, @NotNull Point> { - private Builder(@NotNull String name) { - super(Point.class, name); - } - - @Override - public @NotNull CommandArgument<@NotNull C, @NotNull Point> build() { - return new PointArgument<>(isRequired(), getName(), getDefaultValue(), getSuggestionsProvider(), getDefaultDescription()); - } - } -} diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/RendererArgument.java b/core/src/main/java/net/pl3x/map/core/command/argument/RendererArgument.java deleted file mode 100644 index 575b3e203..000000000 --- a/core/src/main/java/net/pl3x/map/core/command/argument/RendererArgument.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-2023 William Blake Galbreath - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package net.pl3x.map.core.command.argument; - -import cloud.commandframework.ArgumentDescription; -import cloud.commandframework.arguments.CommandArgument; -import cloud.commandframework.context.CommandContext; -import java.util.List; -import java.util.function.BiFunction; -import net.pl3x.map.core.command.argument.parser.RendererParser; -import net.pl3x.map.core.renderer.Renderer; -import org.jetbrains.annotations.NotNull; - -/** - * A {@link Renderer} argument that belongs to a command. - * - * @param command sender type - */ -@SuppressWarnings("unused") -public class RendererArgument extends CommandArgument<@NotNull C, Renderer.@NotNull Builder> { - protected RendererArgument(boolean required, @NotNull String name, @NotNull String defaultValue, @NotNull BiFunction<@NotNull CommandContext<@NotNull C>, @NotNull String, @NotNull List<@NotNull String>> suggestionsProvider, @NotNull ArgumentDescription defaultDescription) { - super(required, name, new RendererParser<>(), defaultValue, Renderer.Builder.class, suggestionsProvider, defaultDescription); - } - - /** - * Create a new {@link RendererArgument} builder. - * - * @param name argument name - * @return new renderer argument builder - */ - public static CommandArgument.@NotNull Builder<@NotNull C, Renderer.@NotNull Builder> builder(@NotNull String name) { - return new RendererArgument.Builder<>(name); - } - - /** - * Create a required {@link RendererArgument}. - * - * @param name argument name - * @return constructed renderer argument - */ - public static @NotNull CommandArgument<@NotNull C, Renderer.@NotNull Builder> of(@NotNull String name) { - return RendererArgument.<@NotNull C>builder(name).asRequired().build(); - } - - /** - * Create an optional {@link RendererArgument}. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @return constructed renderer argument - */ - public static @NotNull CommandArgument<@NotNull C, Renderer.@NotNull Builder> optional(@NotNull String name) { - return RendererArgument.<@NotNull C>builder(name).asOptional().build(); - } - - /** - * Create an optional {@link RendererArgument} with a default value. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @param defaultValue default value that will be used if none was supplied - * @return constructed renderer argument - */ - public static @NotNull CommandArgument<@NotNull C, Renderer.@NotNull Builder> optional(@NotNull String name, @NotNull String defaultValue) { - return RendererArgument.<@NotNull C>builder(name).asOptionalWithDefault(defaultValue).build(); - } - - /** - * Mutable builder for {@link RendererArgument} instances. - * - * @param command sender type - */ - public static class Builder extends CommandArgument.Builder<@NotNull C, Renderer.@NotNull Builder> { - private Builder(@NotNull String name) { - super(Renderer.Builder.class, name); - } - - @Override - public @NotNull CommandArgument<@NotNull C, Renderer.@NotNull Builder> build() { - return new RendererArgument<>(isRequired(), getName(), getDefaultValue(), getSuggestionsProvider(), getDefaultDescription()); - } - } -} diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/WorldArgument.java b/core/src/main/java/net/pl3x/map/core/command/argument/WorldArgument.java deleted file mode 100644 index 58adec0ce..000000000 --- a/core/src/main/java/net/pl3x/map/core/command/argument/WorldArgument.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-2023 William Blake Galbreath - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package net.pl3x.map.core.command.argument; - -import cloud.commandframework.ArgumentDescription; -import cloud.commandframework.arguments.CommandArgument; -import cloud.commandframework.context.CommandContext; -import java.util.List; -import java.util.function.BiFunction; -import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.parser.WorldParser; -import net.pl3x.map.core.command.exception.WorldParseException; -import net.pl3x.map.core.player.Player; -import net.pl3x.map.core.world.World; -import org.jetbrains.annotations.NotNull; - -/** - * A {@link World} argument that belongs to a command. - * - * @param command sender type - */ -@SuppressWarnings("unused") -public class WorldArgument extends CommandArgument<@NotNull C, @NotNull World> { - protected WorldArgument(boolean required, @NotNull String name, @NotNull String defaultValue, @NotNull BiFunction<@NotNull CommandContext<@NotNull C>, @NotNull String, @NotNull List<@NotNull String>> suggestionsProvider, @NotNull ArgumentDescription defaultDescription) { - super(required, name, new WorldParser<>(), defaultValue, World.class, suggestionsProvider, defaultDescription); - } - - /** - * Create a new {@link WorldArgument} builder. - * - * @param name argument name - * @return new world argument builder - */ - public static CommandArgument.@NotNull Builder<@NotNull C, @NotNull World> builder(@NotNull String name) { - return new WorldArgument.Builder<>(name); - } - - /** - * Create a required {@link WorldArgument}. - * - * @param name argument name - * @return constructed world argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull World> of(@NotNull String name) { - return WorldArgument.<@NotNull C>builder(name).asRequired().build(); - } - - /** - * Create an optional {@link WorldArgument}. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @return constructed world argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull World> optional(@NotNull String name) { - return WorldArgument.<@NotNull C>builder(name).asOptional().build(); - } - - /** - * Create an optional {@link WorldArgument} with a default value. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @param defaultValue default value that will be used if none was supplied - * @return constructed world argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull World> optional(@NotNull String name, @NotNull String defaultValue) { - return WorldArgument.<@NotNull C>builder(name).asOptionalWithDefault(defaultValue).build(); - } - - /** - * Resolve {@link World} from command context. - *

- * If context does not contain a {@link World} and the sender is a {@link Player} then the player's world will be used. - * - * @param context command context - * @param name argument name - * @return world - * @throws WorldParseException if context did not contain a {@link World} - * and the sender is not a {@link Player}, or - * the world is not enabled - */ - public static @NotNull World resolve(@NotNull CommandContext<@NotNull Sender> context, @NotNull String name) { - Sender sender = context.getSender(); - World world = context.getOrDefault(name, null); - if (world != null) { - return world; - } - if (sender instanceof Sender.Player player) { - world = player.getWorld(); - if (world == null) { - throw new WorldParseException("unknown", WorldParseException.NO_SUCH_WORLD); - } - if (!world.isEnabled()) { - throw new WorldParseException(world.getName(), WorldParseException.MAP_NOT_ENABLED); - } else { - return world; - } - } - throw new WorldParseException(null, WorldParseException.MUST_SPECIFY_WORLD); - } - - /** - * Mutable builder for {@link WorldArgument} instances. - * - * @param command sender type - */ - public static class Builder extends CommandArgument.Builder<@NotNull C, @NotNull World> { - private Builder(@NotNull String name) { - super(World.class, name); - } - - @Override - public @NotNull CommandArgument<@NotNull C, @NotNull World> build() { - return new WorldArgument<>(isRequired(), getName(), getDefaultValue(), getSuggestionsProvider(), getDefaultDescription()); - } - } -} diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/ZoomArgument.java b/core/src/main/java/net/pl3x/map/core/command/argument/ZoomArgument.java deleted file mode 100644 index cecf6db8c..000000000 --- a/core/src/main/java/net/pl3x/map/core/command/argument/ZoomArgument.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-2023 William Blake Galbreath - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package net.pl3x.map.core.command.argument; - -import cloud.commandframework.ArgumentDescription; -import cloud.commandframework.arguments.CommandArgument; -import cloud.commandframework.context.CommandContext; -import java.util.List; -import java.util.function.BiFunction; -import net.pl3x.map.core.command.argument.parser.ZoomParser; -import org.jetbrains.annotations.NotNull; - -/** - * A {@link Integer} argument that belongs to a command. - * - * @param command sender type - */ -public class ZoomArgument extends CommandArgument<@NotNull C, @NotNull Integer> { - protected ZoomArgument(boolean required, @NotNull String name, @NotNull String defaultValue, @NotNull BiFunction<@NotNull CommandContext<@NotNull C>, @NotNull String, @NotNull List<@NotNull String>> suggestionsProvider, @NotNull ArgumentDescription defaultDescription) { - super(required, name, new ZoomParser<>(), defaultValue, Integer.class, suggestionsProvider, defaultDescription); - } - - /** - * Create a new {@link ZoomArgument} builder. - * - * @param name argument name - * @return new player argument builder - */ - public static CommandArgument.@NotNull Builder<@NotNull C, @NotNull Integer> newBuilder(@NotNull String name) { - return new Builder<>(name); - } - - /** - * Create a required {@link ZoomArgument}. - * - * @param name argument name - * @return constructed player argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull Integer> of(@NotNull String name) { - return ZoomArgument.<@NotNull C>newBuilder(name).asRequired().build(); - } - - /** - * Create an optional {@link ZoomArgument}. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @return constructed player argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull Integer> optional(@NotNull String name) { - return ZoomArgument.<@NotNull C>newBuilder(name).asOptional().build(); - } - - /** - * Create an optional {@link ZoomArgument} with a default value. - *

- * All arguments prior to any other required argument must also be required. - * - * @param name argument name - * @param defaultValue default value that will be used if none was supplied - * @return constructed player argument - */ - public static @NotNull CommandArgument<@NotNull C, @NotNull Integer> optional(@NotNull String name, @NotNull String defaultValue) { - return ZoomArgument.<@NotNull C>newBuilder(name).asOptionalWithDefault(defaultValue).build(); - } - - /** - * Mutable builder for {@link ZoomArgument} instances. - * - * @param command sender type - */ - public static class Builder extends CommandArgument.Builder<@NotNull C, @NotNull Integer> { - private Builder(@NotNull String name) { - super(Integer.class, name); - } - - @Override - public @NotNull CommandArgument<@NotNull C, @NotNull Integer> build() { - return new ZoomArgument<>(isRequired(), getName(), getDefaultValue(), getSuggestionsProvider(), getDefaultDescription()); - } - } -} diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/parser/PlayerParser.java b/core/src/main/java/net/pl3x/map/core/command/argument/parser/PlayerParser.java deleted file mode 100644 index 793beff2a..000000000 --- a/core/src/main/java/net/pl3x/map/core/command/argument/parser/PlayerParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-2023 William Blake Galbreath - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package net.pl3x.map.core.command.argument.parser; - -import cloud.commandframework.arguments.parser.ArgumentParseResult; -import cloud.commandframework.arguments.parser.ArgumentParser; -import cloud.commandframework.context.CommandContext; -import java.util.List; -import java.util.Queue; -import java.util.stream.Collectors; -import net.pl3x.map.core.Pl3xMap; -import net.pl3x.map.core.command.exception.PlayerParseException; -import net.pl3x.map.core.player.Player; -import org.jetbrains.annotations.NotNull; - -/** - * Parser that parses strings into {@link Player}s. - * - * @param command sender type - */ -public class PlayerParser implements ArgumentParser<@NotNull C, @NotNull Player> { - @Override - public @NotNull ArgumentParseResult<@NotNull Player> parse(@NotNull CommandContext<@NotNull C> context, @NotNull Queue<@NotNull String> queue) { - String input = queue.peek(); - if (input == null) { - return ArgumentParseResult.failure(new PlayerParseException(null, PlayerParseException.MUST_SPECIFY_PLAYER)); - } - - Player player = Pl3xMap.api().getPlayerRegistry().get(input); - if (player == null) { - return ArgumentParseResult.failure(new PlayerParseException(input, PlayerParseException.NO_SUCH_PLAYER)); - } - - queue.remove(); - return ArgumentParseResult.success(player); - } - - @Override - public @NotNull List<@NotNull String> suggestions(@NotNull CommandContext<@NotNull C> commandContext, @NotNull String input) { - return Pl3xMap.api().getPlayerRegistry() - .values().stream() - .map(Player::getName) - .collect(Collectors.toList()); - } -} diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/parser/PointParser.java b/core/src/main/java/net/pl3x/map/core/command/argument/parser/PointParser.java deleted file mode 100644 index 99c9e31b8..000000000 --- a/core/src/main/java/net/pl3x/map/core/command/argument/parser/PointParser.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-2023 William Blake Galbreath - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package net.pl3x.map.core.command.argument.parser; - -import cloud.commandframework.arguments.parser.ArgumentParseResult; -import cloud.commandframework.arguments.parser.ArgumentParser; -import cloud.commandframework.arguments.standard.IntegerArgument; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.exceptions.parsing.NoInputProvidedException; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import net.pl3x.map.core.command.exception.PointParseException; -import net.pl3x.map.core.log.Logger; -import net.pl3x.map.core.markers.Point; -import org.jetbrains.annotations.NotNull; - -/** - * Parser that parses strings into {@link Point}s. - * - * @param command sender type - */ -public class PointParser implements ArgumentParser<@NotNull C, @NotNull Point> { - @Override - public @NotNull ArgumentParseResult<@NotNull Point> parse(@NotNull CommandContext<@NotNull C> context, @NotNull Queue<@NotNull String> queue) { - if (queue.size() < 2) { - StringBuilder input = new StringBuilder(); - for (int i = 0; i < queue.size(); i++) { - input.append(((LinkedList) queue).get(i)); - } - return ArgumentParseResult.failure(new PointParseException(input.toString(), PointParseException.INVALID_FORMAT)); - } - Integer[] coordinates = new Integer[2]; - for (int i = 0; i < 2; i++) { - ArgumentParseResult coordinate = parseCoord(context, queue); - if (coordinate.getFailure().isPresent()) { - return ArgumentParseResult.failure(coordinate.getFailure().get()); - } - coordinates[i] = coordinate.getParsedValue().orElseThrow(NullPointerException::new); - } - - return ArgumentParseResult.success(new Point(coordinates[0], coordinates[1])); - } - - @Override - public @NotNull List<@NotNull String> suggestions(@NotNull CommandContext<@NotNull C> commandContext, @NotNull String input) { - return IntegerArgument.IntegerParser.getSuggestions(Integer.MIN_VALUE, Integer.MAX_VALUE, input); - } - - public @NotNull ArgumentParseResult<@NotNull Integer> parseCoord(@NotNull CommandContext<@NotNull C> context, @NotNull Queue<@NotNull String> queue) { - String input = queue.peek(); - if (input == null) { - return ArgumentParseResult.failure(new NoInputProvidedException(PointParser.class, context)); - } - - int coordinate; - try { - coordinate = input.isEmpty() ? 0 : Integer.parseInt(input); - } catch (Exception e) { - Logger.severe("Failed to get integer from coordinate input", e); - return ArgumentParseResult.failure(new IntegerArgument.IntegerParseException( - input, - new IntegerArgument.IntegerParser<>( - IntegerArgument.IntegerParser.DEFAULT_MINIMUM, - IntegerArgument.IntegerParser.DEFAULT_MAXIMUM - ), - context - )); - } - - queue.remove(); - return ArgumentParseResult.success(coordinate); - } -} diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/ConfirmCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/ConfirmCommand.java index 16443316f..0983d5f77 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/ConfirmCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/ConfirmCommand.java @@ -23,8 +23,8 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.extra.confirmation.CommandConfirmationManager; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; +import com.google.common.cache.CacheBuilder; +import java.time.Duration; import java.util.concurrent.TimeUnit; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -32,17 +32,27 @@ import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; import net.pl3x.map.core.configuration.Lang; +import org.incendo.cloud.description.CommandDescription; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.minecraft.extras.RichDescription; +import org.incendo.cloud.processors.cache.CloudCache; +import org.incendo.cloud.processors.cache.GuavaCache; +import org.incendo.cloud.processors.confirmation.ConfirmationConfiguration; +import org.incendo.cloud.processors.confirmation.ConfirmationManager; import org.jetbrains.annotations.NotNull; public class ConfirmCommand extends Pl3xMapCommand { - private final CommandConfirmationManager<@NotNull Sender> confirmationManager = new CommandConfirmationManager<>( - 15L, TimeUnit.SECONDS, - context -> context.getCommandContext().getSender().sendMessage( - Component.text().append(Lang.parse(Lang.COMMAND_CONFIRM_CONFIRMATION_REQUIRED_MESSAGE)) - .hoverEvent(Lang.parse(Lang.CLICK_TO_CONFIRM)) - .clickEvent(ClickEvent.runCommand("/map confirm")) - ), - sender -> sender.sendMessage(Lang.COMMAND_CONFIRM_NO_PENDING_MESSAGE) + private final ConfirmationManager<@NotNull Sender> confirmationManager = ConfirmationManager.confirmationManager( + ConfirmationConfiguration.builder() + .cache(GuavaCache.of(CacheBuilder.newBuilder().build())) + .noPendingCommandNotifier(sender -> sender.sendMessage(Lang.COMMAND_CONFIRM_NO_PENDING_MESSAGE)) + .confirmationRequiredNotifier((sender, senderConfirmationContext) -> sender.sendMessage( + Component.text().append(Lang.parse(Lang.COMMAND_CONFIRM_CONFIRMATION_REQUIRED_MESSAGE)) + .hoverEvent(Lang.parse(Lang.CLICK_TO_CONFIRM)) + .clickEvent(ClickEvent.runCommand("/map confirm")) + )) + .expiration(Duration.ofSeconds(15L)) + .build() ); public ConfirmCommand(@NotNull CommandHandler handler) { @@ -51,11 +61,11 @@ public ConfirmCommand(@NotNull CommandHandler handler) { @Override public void register() { - this.confirmationManager.registerConfirmationProcessor(getHandler().getManager()); + getHandler().getManager().registerCommandPostProcessor(this.confirmationManager.createPostprocessor()); getHandler().registerSubcommand(builder -> builder.literal("confirm") - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_CONFIRM_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_CONFIRM_DESCRIPTION))) .permission("pl3xmap.command.confirm") - .handler(this.confirmationManager.createConfirmationExecutionHandler())); + .handler(this.confirmationManager.createExecutionHandler())); } } diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/FullRenderCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/FullRenderCommand.java index b5c02f944..380b84077 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/FullRenderCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/FullRenderCommand.java @@ -23,20 +23,20 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import java.util.Collection; import java.util.concurrent.CompletableFuture; import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.WorldArgument; +import net.pl3x.map.core.command.parser.WorldParser; import net.pl3x.map.core.configuration.Config; import net.pl3x.map.core.configuration.Lang; import net.pl3x.map.core.log.Logger; import net.pl3x.map.core.markers.Point; import net.pl3x.map.core.world.World; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; public class FullRenderCommand extends Pl3xMapCommand { @@ -47,8 +47,8 @@ public FullRenderCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("fullrender") - .argument(WorldArgument.of("world"), description(Lang.COMMAND_ARGUMENT_REQUIRED_WORLD_DESCRIPTION)) - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_FULLRENDER_DESCRIPTION)) + .required("world", WorldParser.parser(), description(Lang.COMMAND_ARGUMENT_REQUIRED_WORLD_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_FULLRENDER_DESCRIPTION))) .permission("pl3xmap.command.fullrender") .handler(this::execute)); } @@ -58,7 +58,7 @@ public void execute(@NotNull CommandContext<@NotNull Sender> context) { } private void executeAsync(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); + Sender sender = context.sender(); World world = context.get("world"); Collection regions = world.listRegions(true); diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/HelpCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/HelpCommand.java index 1ed6074bb..372791e0d 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/HelpCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/HelpCommand.java @@ -23,16 +23,19 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.CommandHelpHandler.VerboseHelpEntry; -import cloud.commandframework.arguments.standard.StringArgument; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; -import cloud.commandframework.minecraft.extras.MinecraftHelp; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; import net.pl3x.map.core.configuration.Lang; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.help.result.CommandEntry; +import org.incendo.cloud.minecraft.extras.AudienceProvider; +import org.incendo.cloud.minecraft.extras.MinecraftHelp; +import org.incendo.cloud.minecraft.extras.RichDescription; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.SuggestionProvider; import org.jetbrains.annotations.NotNull; public class HelpCommand extends Pl3xMapCommand { @@ -40,30 +43,34 @@ public class HelpCommand extends Pl3xMapCommand { public HelpCommand(@NotNull CommandHandler handler) { super(handler); - this.minecraftHelp = MinecraftHelp.createNative("/map help", handler.getManager()); - this.minecraftHelp.setHelpColors(MinecraftHelp.HelpColors.of( - TextColor.color(0x5B00FF), - NamedTextColor.WHITE, - TextColor.color(0xC028FF), - NamedTextColor.GRAY, - NamedTextColor.DARK_GRAY - )); - this.minecraftHelp.setMessage(MinecraftHelp.MESSAGE_HELP_TITLE, "Pl3xMap Help"); + this.minecraftHelp = MinecraftHelp.builder() + .commandManager(handler.getManager()) + .audienceProvider(AudienceProvider.nativeAudience()) + .commandPrefix("/map help") + .colors(MinecraftHelp.helpColors( + TextColor.color(0x5B00FF), + NamedTextColor.WHITE, + TextColor.color(0xC028FF), + NamedTextColor.GRAY, + NamedTextColor.DARK_GRAY + )) + .messages(MinecraftHelp.MESSAGE_HELP_TITLE, "Pl3xMap Help") + .build(); } @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("help") - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_HELP_DESCRIPTION)) - .argument(StringArgument.builder("query").greedy().asOptional() - .withSuggestionsProvider((context, input) -> getHandler().getManager() - .createCommandHelpHandler().queryRootIndex(context.getSender()) - .getEntries().stream().map(VerboseHelpEntry::getSyntaxString).toList()) - .build(), description(Lang.COMMAND_HELP_ARGUMENT_QUERY_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_HELP_DESCRIPTION))) + .optional(CommandComponent.ofType(String.class, "query") + .parser(StringParser.greedyStringParser()) + .suggestionProvider(SuggestionProvider.blockingStrings((context, input) -> getHandler().getManager() + .createHelpHandler().queryRootIndex(context.sender()) + .entries().stream().map(CommandEntry::syntax).toList()))) .permission("pl3xmap.command.help") .handler(ctx -> { - String query = ctx.getOptional("query").orElse(""); - this.minecraftHelp.queryCommands(query, ctx.getSender()); + String query = ctx.optional("query").orElse(""); + this.minecraftHelp.queryCommands(query, ctx.sender()); })); } } diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/HideCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/HideCommand.java index 7c3565a49..fb24fb474 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/HideCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/HideCommand.java @@ -23,15 +23,14 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.PlayerArgument; import net.pl3x.map.core.configuration.Lang; import net.pl3x.map.core.player.Player; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; public class HideCommand extends Pl3xMapCommand { @@ -42,19 +41,25 @@ public HideCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("hide") - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_HIDE_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_HIDE_DESCRIPTION))) .permission("pl3xmap.command.hide") .handler(this::execute)); getHandler().registerSubcommand(builder -> builder.literal("hide") - .argument(PlayerArgument.optional("player"), description(Lang.COMMAND_ARGUMENT_OPTIONAL_PLAYER_DESCRIPTION)) - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_HIDE_DESCRIPTION)) + .optional("player", getHandler().getPlatformParsers().playerSelectorParser(), + description(Lang.COMMAND_ARGUMENT_OPTIONAL_PLAYER_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_HIDE_DESCRIPTION))) .permission("pl3xmap.command.hide.others") .handler(this::execute)); } private void execute(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); - Player player = PlayerArgument.resolve(context, "player"); + Sender sender = context.sender(); + Player player = getHandler().getPlatformParsers().resolvePlayerFromPlayerSelector("player", context); + + if (player == null) { + sender.sendMessage(Lang.ERROR_MUST_SPECIFY_PLAYER); + return; + } if (player.isHidden()) { sender.sendMessage(Lang.COMMAND_HIDE_ALREADY_HIDDEN, @@ -67,4 +72,6 @@ private void execute(@NotNull CommandContext<@NotNull Sender> context) { sender.sendMessage(Lang.COMMAND_HIDE_SUCCESS, Placeholder.unparsed("player", player.getName())); } + + } diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/PauseCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/PauseCommand.java index 241f5a783..ddaede302 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/PauseCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/PauseCommand.java @@ -23,14 +23,14 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; import net.pl3x.map.core.configuration.Lang; import net.pl3x.map.core.renderer.task.RegionProcessor; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; public class PauseCommand extends Pl3xMapCommand { @@ -41,13 +41,13 @@ public PauseCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("pause") - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_PAUSE_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_PAUSE_DESCRIPTION))) .permission("pl3xmap.command.pause") .handler(this::execute)); } private void execute(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); + Sender sender = context.sender(); RegionProcessor processor = Pl3xMap.api().getRegionProcessor(); diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/RadiusRenderCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/RadiusRenderCommand.java index 59da5fd8e..2f6e56d82 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/RadiusRenderCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/RadiusRenderCommand.java @@ -23,22 +23,21 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.arguments.standard.IntegerArgument; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import java.util.Collection; import java.util.concurrent.CompletableFuture; import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.PointArgument; -import net.pl3x.map.core.command.argument.WorldArgument; +import net.pl3x.map.core.command.parser.WorldParser; import net.pl3x.map.core.configuration.Config; import net.pl3x.map.core.configuration.Lang; import net.pl3x.map.core.log.Logger; import net.pl3x.map.core.markers.Point; import net.pl3x.map.core.world.World; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; +import org.incendo.cloud.parser.standard.IntegerParser; import org.jetbrains.annotations.NotNull; public class RadiusRenderCommand extends Pl3xMapCommand { @@ -49,10 +48,10 @@ public RadiusRenderCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("radiusrender") - .argument(WorldArgument.of("world"), description(Lang.COMMAND_ARGUMENT_REQUIRED_WORLD_DESCRIPTION)) - .argument(IntegerArgument.builder("radius").withMin(1).withMax(1000000).build()) - .argument(PointArgument.optional("center"), description(Lang.COMMAND_ARGUMENT_OPTIONAL_CENTER_DESCRIPTION)) - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_RADIUSRENDER_DESCRIPTION)) + .required("world", WorldParser.parser(), description(Lang.COMMAND_ARGUMENT_REQUIRED_WORLD_DESCRIPTION)) + .required("radius", IntegerParser.integerParser(1, 1000000)) + .optional("center", getHandler().getPlatformParsers().columnPosParser(), description(Lang.COMMAND_ARGUMENT_OPTIONAL_CENTER_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_RADIUSRENDER_DESCRIPTION))) .permission("pl3xmap.command.radiusrender") .handler(this::execute)); } @@ -62,10 +61,10 @@ public void execute(@NotNull CommandContext<@NotNull Sender> context) { } private void executeAsync(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); + Sender sender = context.sender(); World world = context.get("world"); int radius = context.get("radius"); - Point center = PointArgument.resolve(context, "center"); + Point center = getHandler().getPlatformParsers().resolvePointFromColumnPos("center", context); int rX = center.x() >> 9; int rZ = center.z() >> 9; diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/ReloadCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/ReloadCommand.java index ec7a7a69e..d0f105b59 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/ReloadCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/ReloadCommand.java @@ -23,14 +23,14 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; import net.pl3x.map.core.configuration.Lang; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; public class ReloadCommand extends Pl3xMapCommand { @@ -41,7 +41,7 @@ public ReloadCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("reload") - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_RELOAD_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_RELOAD_DESCRIPTION))) .permission("pl3xmap.command.reload") .handler(this::execute)); } @@ -51,7 +51,7 @@ public void execute(@NotNull CommandContext<@NotNull Sender> context) { Pl3xMap.api().enable(); - context.getSender().sendMessage(Lang.COMMAND_RELOAD_SUCCESS, + context.sender().sendMessage(Lang.COMMAND_RELOAD_SUCCESS, Placeholder.unparsed("version", Pl3xMap.api().getVersion())); } } diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/ResetMapCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/ResetMapCommand.java index f82e79295..2b8ac1341 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/ResetMapCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/ResetMapCommand.java @@ -23,9 +23,6 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.extra.confirmation.CommandConfirmationManager; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import java.io.IOException; import java.util.concurrent.CompletableFuture; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; @@ -34,10 +31,13 @@ import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.WorldArgument; +import net.pl3x.map.core.command.parser.WorldParser; import net.pl3x.map.core.configuration.Lang; import net.pl3x.map.core.util.FileUtil; import net.pl3x.map.core.world.World; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; +import org.incendo.cloud.processors.confirmation.ConfirmationManager; import org.jetbrains.annotations.NotNull; public class ResetMapCommand extends Pl3xMapCommand { @@ -48,9 +48,9 @@ public ResetMapCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("resetmap") - .argument(WorldArgument.of("world"), description(Lang.COMMAND_ARGUMENT_REQUIRED_WORLD_DESCRIPTION)) - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_RESETMAP_DESCRIPTION)) - .meta(CommandConfirmationManager.META_CONFIRMATION_REQUIRED, true) + .required("world", WorldParser.parser(), description(Lang.COMMAND_ARGUMENT_REQUIRED_WORLD_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_RESETMAP_DESCRIPTION))) + .meta(ConfirmationManager.META_CONFIRMATION_REQUIRED, true) .permission("pl3xmap.command.resetmap") .handler(this::execute)); } @@ -60,7 +60,7 @@ private void execute(@NotNull CommandContext<@NotNull Sender> context) { } private void executeAsync(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); + Sender sender = context.sender(); World world = context.get("world"); TagResolver.Single worldPlaceholder = Placeholder.unparsed("world", world.getName()); diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/ResumeCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/ResumeCommand.java index 430b0aba4..3dd6e46c1 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/ResumeCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/ResumeCommand.java @@ -23,14 +23,14 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; import net.pl3x.map.core.configuration.Lang; import net.pl3x.map.core.renderer.task.RegionProcessor; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; public class ResumeCommand extends Pl3xMapCommand { @@ -41,13 +41,13 @@ public ResumeCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("resume") - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_RESUME_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_RESUME_DESCRIPTION))) .permission("pl3xmap.command.resume") .handler(this::execute)); } private void execute(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); + Sender sender = context.sender(); RegionProcessor processor = Pl3xMap.api().getRegionProcessor(); diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/ShowCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/ShowCommand.java index fb46a3d36..9851061fd 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/ShowCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/ShowCommand.java @@ -23,15 +23,14 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.PlayerArgument; import net.pl3x.map.core.configuration.Lang; import net.pl3x.map.core.player.Player; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; public class ShowCommand extends Pl3xMapCommand { @@ -42,19 +41,24 @@ public ShowCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("show") - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_SHOW_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_SHOW_DESCRIPTION))) .permission("pl3xmap.command.show") .handler(this::execute)); getHandler().registerSubcommand(builder -> builder.literal("show") - .argument(PlayerArgument.optional("player"), description(Lang.COMMAND_ARGUMENT_OPTIONAL_PLAYER_DESCRIPTION)) - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_SHOW_DESCRIPTION)) + .optional("player", getHandler().getPlatformParsers().playerSelectorParser(), description(Lang.COMMAND_ARGUMENT_OPTIONAL_PLAYER_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_SHOW_DESCRIPTION))) .permission("pl3xmap.command.show.others") .handler(this::execute)); } private void execute(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); - Player target = PlayerArgument.resolve(context, "player"); + Sender sender = context.sender(); + Player target = getHandler().getPlatformParsers().resolvePlayerFromPlayerSelector("player", context); + + if (target == null) { + sender.sendMessage(Lang.ERROR_MUST_SPECIFY_PLAYER); + return; + } if (!target.isHidden()) { sender.sendMessage(Lang.COMMAND_SHOW_NOT_HIDDEN, diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/StatusCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/StatusCommand.java index 8e44b1d88..d30654bc8 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/StatusCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/StatusCommand.java @@ -23,8 +23,6 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import java.util.Set; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.pl3x.map.core.Pl3xMap; @@ -35,6 +33,8 @@ import net.pl3x.map.core.renderer.progress.Progress; import net.pl3x.map.core.renderer.task.RegionProcessor; import net.pl3x.map.core.world.World; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; public class StatusCommand extends Pl3xMapCommand { @@ -45,13 +45,13 @@ public StatusCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("status") - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_STATUS_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_STATUS_DESCRIPTION))) .permission("pl3xmap.command.status") .handler(this::execute)); } public void execute(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); + Sender sender = context.sender(); String lineNext = "├─"; String lineLast = "└─"; diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/StitchCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/StitchCommand.java index 6fe584cab..1b2cb1386 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/StitchCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/StitchCommand.java @@ -23,8 +23,6 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.IOException; @@ -38,9 +36,9 @@ import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.RendererArgument; -import net.pl3x.map.core.command.argument.WorldArgument; -import net.pl3x.map.core.command.argument.ZoomArgument; +import net.pl3x.map.core.command.parser.RendererParser; +import net.pl3x.map.core.command.parser.WorldParser; +import net.pl3x.map.core.command.parser.ZoomParser; import net.pl3x.map.core.configuration.Config; import net.pl3x.map.core.configuration.Lang; import net.pl3x.map.core.image.io.IO; @@ -48,6 +46,8 @@ import net.pl3x.map.core.markers.Point; import net.pl3x.map.core.renderer.Renderer; import net.pl3x.map.core.world.World; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -59,10 +59,10 @@ public StitchCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("stitch") - .argument(WorldArgument.of("world"), description(Lang.COMMAND_ARGUMENT_REQUIRED_WORLD_DESCRIPTION)) - .argument(RendererArgument.of("renderer"), description(Lang.COMMAND_ARGUMENT_REQUIRED_RENDERER_DESCRIPTION)) - .argument(ZoomArgument.optional("zoom"), description(Lang.COMMAND_ARGUMENT_OPTIONAL_ZOOM_DESCRIPTION)) - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_STITCH_DESCRIPTION)) + .required("world", WorldParser.parser(), description(Lang.COMMAND_ARGUMENT_REQUIRED_WORLD_DESCRIPTION)) + .required("renderer", RendererParser.parser(), description(Lang.COMMAND_ARGUMENT_REQUIRED_RENDERER_DESCRIPTION)) + .optional("zoom", ZoomParser.parser(), description(Lang.COMMAND_ARGUMENT_OPTIONAL_ZOOM_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_STITCH_DESCRIPTION))) .permission("pl3xmap.command.stitch") .handler(this::execute)); } @@ -72,7 +72,7 @@ private void execute(@NotNull CommandContext<@NotNull Sender> context) { } private void executeAsync(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); + Sender sender = context.sender(); World world = context.get("world"); Renderer.Builder renderer = context.get("renderer"); int zoom = context.getOrDefault("zoom", 0); diff --git a/core/src/main/java/net/pl3x/map/core/command/commands/VersionCommand.java b/core/src/main/java/net/pl3x/map/core/command/commands/VersionCommand.java index c3db31aa3..75ecc8558 100644 --- a/core/src/main/java/net/pl3x/map/core/command/commands/VersionCommand.java +++ b/core/src/main/java/net/pl3x/map/core/command/commands/VersionCommand.java @@ -23,8 +23,6 @@ */ package net.pl3x.map.core.command.commands; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -40,6 +38,8 @@ import net.pl3x.map.core.command.Pl3xMapCommand; import net.pl3x.map.core.command.Sender; import net.pl3x.map.core.configuration.Lang; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.extras.RichDescription; import org.jetbrains.annotations.NotNull; public class VersionCommand extends Pl3xMapCommand { @@ -56,13 +56,13 @@ public VersionCommand(@NotNull CommandHandler handler) { @Override public void register() { getHandler().registerSubcommand(builder -> builder.literal("version") - .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_VERSION_DESCRIPTION)) + .commandDescription(RichDescription.of(Lang.parse(Lang.COMMAND_VERSION_DESCRIPTION))) .permission("pl3xmap.command.version") .handler(this::execute)); } public void execute(@NotNull CommandContext<@NotNull Sender> context) { - Sender sender = context.getSender(); + Sender sender = context.sender(); long now = System.currentTimeMillis(); if (this.lastChecked + TimeUnit.SECONDS.toMillis(15) > now) { @@ -89,7 +89,7 @@ public void execute(@NotNull CommandContext<@NotNull Sender> context) { JsonElement elem = JsonParser.parseString(json); if (elem.isJsonArray()) { JsonArray arr = elem.getAsJsonArray(); - if (arr.size() > 0) { + if (!arr.isEmpty()) { JsonElement elem1 = arr.get(0); if (elem1.isJsonObject()) { JsonObject obj = elem1.getAsJsonObject(); @@ -117,6 +117,12 @@ public void execute(@NotNull CommandContext<@NotNull Sender> context) { } private void showVersion(Sender sender) { + sender.sendMessage(Lang.COMMAND_VERSION_SUCCESS, + Placeholder.unparsed("version", Pl3xMap.api().getVersion()), + Placeholder.unparsed("platform", Pl3xMap.api().getPlatform()), + Placeholder.unparsed("commit", Pl3xMap.api().getVersionCommit()) + ); + if (this.version.startsWith("-")) { sender.sendMessage(switch (this.version) { case "-1" -> Lang.COMMAND_VERSION_STILL_CHECKING; @@ -127,12 +133,6 @@ private void showVersion(Sender sender) { return; } - sender.sendMessage(Lang.COMMAND_VERSION_SUCCESS, - Placeholder.unparsed("version", Pl3xMap.api().getVersion()), - Placeholder.unparsed("platform", Pl3xMap.api().getPlatform()), - Placeholder.unparsed("commit", Pl3xMap.api().getVersionCommit()) - ); - int cur_build; int new_build; diff --git a/core/src/main/java/net/pl3x/map/core/command/parser/PlatformParsers.java b/core/src/main/java/net/pl3x/map/core/command/parser/PlatformParsers.java new file mode 100644 index 000000000..41d1a4c16 --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/command/parser/PlatformParsers.java @@ -0,0 +1,17 @@ +package net.pl3x.map.core.command.parser; + +import net.pl3x.map.core.command.Sender; +import net.pl3x.map.core.markers.Point; +import net.pl3x.map.core.player.Player; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.ParserDescriptor; + +public interface PlatformParsers { + ParserDescriptor columnPosParser(); + + Point resolvePointFromColumnPos(String name, CommandContext context); + + ParserDescriptor playerSelectorParser(); + + Player resolvePlayerFromPlayerSelector(String name, CommandContext context); +} diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/parser/RendererParser.java b/core/src/main/java/net/pl3x/map/core/command/parser/RendererParser.java similarity index 71% rename from core/src/main/java/net/pl3x/map/core/command/argument/parser/RendererParser.java rename to core/src/main/java/net/pl3x/map/core/command/parser/RendererParser.java index 13f2d5ae6..d2961bda8 100644 --- a/core/src/main/java/net/pl3x/map/core/command/argument/parser/RendererParser.java +++ b/core/src/main/java/net/pl3x/map/core/command/parser/RendererParser.java @@ -21,18 +21,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package net.pl3x.map.core.command.argument.parser; +package net.pl3x.map.core.command.parser; -import cloud.commandframework.arguments.parser.ArgumentParseResult; -import cloud.commandframework.arguments.parser.ArgumentParser; -import cloud.commandframework.context.CommandContext; -import java.util.List; -import java.util.Queue; import java.util.stream.Collectors; import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.command.exception.RendererParseException; import net.pl3x.map.core.registry.RendererRegistry; import net.pl3x.map.core.renderer.Renderer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.jetbrains.annotations.NotNull; /** @@ -40,10 +42,14 @@ * * @param command sender type */ -public class RendererParser implements ArgumentParser<@NotNull C, Renderer.@NotNull Builder> { +public class RendererParser implements ArgumentParser<@NotNull C, Renderer.@NotNull Builder>, BlockingSuggestionProvider.Strings { + public static ParserDescriptor parser() { + return ParserDescriptor.of(new RendererParser<>(), Renderer.Builder.class); + } + @Override - public @NotNull ArgumentParseResult parse(@NotNull CommandContext<@NotNull C> context, @NotNull Queue<@NotNull String> queue) { - String input = queue.peek(); + public @NotNull ArgumentParseResult parse(@NotNull CommandContext<@NotNull C> commandContext, @NotNull CommandInput commandInput) { + String input = commandInput.peekString(); if (input == null) { return ArgumentParseResult.failure(new RendererParseException(null, RendererParseException.MUST_SPECIFY_RENDERER)); } @@ -53,12 +59,12 @@ public class RendererParser implements ArgumentParser<@NotNull C, Renderer.@N return ArgumentParseResult.failure(new RendererParseException(input, RendererParseException.NO_SUCH_RENDERER)); } - queue.remove(); + commandInput.readString(); return ArgumentParseResult.success(builder); } @Override - public @NotNull List<@NotNull String> suggestions(@NotNull CommandContext<@NotNull C> commandContext, @NotNull String input) { + public @NonNull Iterable<@NonNull String> stringSuggestions(@NonNull CommandContext commandContext, @NonNull CommandInput input) { return Pl3xMap.api().getRendererRegistry() .values().stream() .map(Renderer.Builder::getKey) diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/parser/WorldParser.java b/core/src/main/java/net/pl3x/map/core/command/parser/WorldParser.java similarity index 56% rename from core/src/main/java/net/pl3x/map/core/command/argument/parser/WorldParser.java rename to core/src/main/java/net/pl3x/map/core/command/parser/WorldParser.java index 7a2213e3d..2fb97b492 100644 --- a/core/src/main/java/net/pl3x/map/core/command/argument/parser/WorldParser.java +++ b/core/src/main/java/net/pl3x/map/core/command/parser/WorldParser.java @@ -21,17 +21,20 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package net.pl3x.map.core.command.argument.parser; +package net.pl3x.map.core.command.parser; -import cloud.commandframework.arguments.parser.ArgumentParseResult; -import cloud.commandframework.arguments.parser.ArgumentParser; -import cloud.commandframework.context.CommandContext; -import java.util.List; -import java.util.Queue; import java.util.stream.Collectors; import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.command.Sender; import net.pl3x.map.core.command.exception.WorldParseException; import net.pl3x.map.core.world.World; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.jetbrains.annotations.NotNull; /** @@ -39,10 +42,14 @@ * * @param command sender type */ -public class WorldParser implements ArgumentParser<@NotNull C, @NotNull World> { +public class WorldParser implements ArgumentParser<@NotNull C, @NotNull World>, BlockingSuggestionProvider.Strings { + public static ParserDescriptor parser() { + return ParserDescriptor.of(new WorldParser<>(), World.class); + } + @Override - public @NotNull ArgumentParseResult<@NotNull World> parse(@NotNull CommandContext<@NotNull C> context, @NotNull Queue<@NotNull String> queue) { - String input = queue.peek(); + public @NonNull ArgumentParseResult<@NonNull @NotNull World> parse(@NonNull CommandContext<@NonNull @NotNull C> commandContext, @NonNull CommandInput commandInput) { + String input = commandInput.peekString(); if (input == null) { return ArgumentParseResult.failure(new WorldParseException(null, WorldParseException.MUST_SPECIFY_WORLD)); } @@ -61,12 +68,32 @@ public class WorldParser implements ArgumentParser<@NotNull C, @NotNull World return ArgumentParseResult.failure(new WorldParseException(input, WorldParseException.MAP_NOT_ENABLED)); } - queue.remove(); + commandInput.readString(); return ArgumentParseResult.success(world); } + public static @NotNull World resolveWorld(@NotNull CommandContext<@NotNull Sender> context, @NotNull String name) { + Sender sender = context.sender(); + World world = context.getOrDefault(name, null); + if (world != null) { + return world; + } + if (sender instanceof Sender.Player player) { + world = player.getWorld(); + if (world == null) { + throw new WorldParseException("unknown", WorldParseException.NO_SUCH_WORLD); + } + if (!world.isEnabled()) { + throw new WorldParseException(world.getName(), WorldParseException.MAP_NOT_ENABLED); + } else { + return world; + } + } + throw new WorldParseException(null, WorldParseException.MUST_SPECIFY_WORLD); + } + @Override - public @NotNull List<@NotNull String> suggestions(@NotNull CommandContext<@NotNull C> commandContext, @NotNull String input) { + public @NonNull Iterable<@NonNull String> stringSuggestions(@NonNull CommandContext commandContext, @NonNull CommandInput input) { return Pl3xMap.api().getWorldRegistry() .values().stream() .filter(World::isEnabled) diff --git a/core/src/main/java/net/pl3x/map/core/command/argument/parser/ZoomParser.java b/core/src/main/java/net/pl3x/map/core/command/parser/ZoomParser.java similarity index 67% rename from core/src/main/java/net/pl3x/map/core/command/argument/parser/ZoomParser.java rename to core/src/main/java/net/pl3x/map/core/command/parser/ZoomParser.java index 46b500489..53ea651b5 100644 --- a/core/src/main/java/net/pl3x/map/core/command/argument/parser/ZoomParser.java +++ b/core/src/main/java/net/pl3x/map/core/command/parser/ZoomParser.java @@ -21,21 +21,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package net.pl3x.map.core.command.argument.parser; +package net.pl3x.map.core.command.parser; -import cloud.commandframework.arguments.parser.ArgumentParseResult; -import cloud.commandframework.arguments.parser.ArgumentParser; -import cloud.commandframework.arguments.standard.IntegerArgument; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Queue; import java.util.Set; import java.util.TreeSet; import net.pl3x.map.core.command.exception.ZoomParseException; import net.pl3x.map.core.world.World; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.jetbrains.annotations.NotNull; /** @@ -43,39 +44,43 @@ * * @param command sender type */ -public class ZoomParser implements ArgumentParser { +public class ZoomParser implements ArgumentParser, BlockingSuggestionProvider.Strings { + public static ParserDescriptor parser() { + return ParserDescriptor.of(new ZoomParser<>(), Integer.class); + } + @Override - public @NotNull ArgumentParseResult parse(@NotNull CommandContext context, @NotNull Queue<@NotNull String> inputQueue) { - String input = inputQueue.peek(); - if (input == null) { - return ArgumentParseResult.failure(new NoInputProvidedException(IntegerArgument.IntegerParser.class, context)); - } + public @NonNull ArgumentParseResult<@NonNull Integer> parse(@NonNull CommandContext<@NonNull C> commandContext, @NonNull CommandInput commandInput) { + String input = commandInput.peekString(); try { int zoom = Integer.parseInt(input); - if (zoom < 0 || zoom > getMax(context)) { + if (zoom < 0 || zoom > getMax(commandContext)) { return ArgumentParseResult.failure(new ZoomParseException(input, ZoomParseException.NOT_VALID_ZOOM_LEVEL)); } - inputQueue.remove(); + commandInput.readString(); return ArgumentParseResult.success(zoom); } catch (Exception e) { return ArgumentParseResult.failure(new ZoomParseException(input, ZoomParseException.NOT_VALID_ZOOM_LEVEL)); } } + @Override - public @NotNull List<@NotNull String> suggestions(@NotNull CommandContext context, @NotNull String input) { - Set numbers = new TreeSet<>(); + public @NonNull Iterable<@NonNull String> stringSuggestions(@NonNull CommandContext commandContext, @NonNull CommandInput input) { + final Set numbers = new TreeSet<>(); + final String token = input.peekString(); + try { - long inputNum = Long.parseLong(input.equals("-") ? "-0" : input.isEmpty() ? "0" : input); - long inputNumAbsolute = Math.abs(inputNum); + final long inputNum = Long.parseLong(token.equals("-") ? "-0" : token.isEmpty() ? "0" : token); + final long inputNumAbsolute = Math.abs(inputNum); numbers.add(inputNumAbsolute); /* It's a valid number, so we suggest it */ - int max = getMax(context); + int max = getMax(commandContext); for (int i = 0; i < 10 && (inputNum * 10) + i <= max; i++) { numbers.add((inputNumAbsolute * 10) + i); } List suggestions = new LinkedList<>(); for (long number : numbers) { - if (input.startsWith("-")) { + if (token.startsWith("-")) { number = -number; /* Preserve sign */ } if (number < 0 || number > max) { diff --git a/core/src/main/java/net/pl3x/map/core/configuration/Config.java b/core/src/main/java/net/pl3x/map/core/configuration/Config.java index a0c4a2cae..348b200ff 100644 --- a/core/src/main/java/net/pl3x/map/core/configuration/Config.java +++ b/core/src/main/java/net/pl3x/map/core/configuration/Config.java @@ -118,6 +118,17 @@ How many scroll pixels (as reported by L.DomEvent.getWheelDelta) mean for security reasons. But you do you, boo boo.""") public static boolean HTTPD_FOLLOW_SYMLINKS = false; + @Key("settings.performance.live-update.enabled") + @Comment(""" + Whether or not the real-time marker system should run.""") + public static boolean LIVE_UPDATE_ENABLED = true; + + @Key("settings.performance.live-update.threads") + @Comment(""" + The number of process-threads to use for real-time marker updates on the map. + Value of -1 will use 50% of the available cpu-threads. (recommended)""") + public static int LIVE_UPDATE_THREADS = -1; + @Key("settings.performance.render-threads") @Comment(""" The number of process-threads to use for loading and scanning chunks. diff --git a/core/src/main/java/net/pl3x/map/core/configuration/PlayersLayerConfig.java b/core/src/main/java/net/pl3x/map/core/configuration/PlayersLayerConfig.java index 7ccbdd848..81380d5d9 100644 --- a/core/src/main/java/net/pl3x/map/core/configuration/PlayersLayerConfig.java +++ b/core/src/main/java/net/pl3x/map/core/configuration/PlayersLayerConfig.java @@ -44,9 +44,12 @@ public final class PlayersLayerConfig extends AbstractConfig { @Key("settings.layer.update-interval") @Comment(""" - How often (in seconds) to update the marker. - Setting to 0 is the same as setting it to 1.""") + How often (in seconds) to update the marker.""") public static int UPDATE_INTERVAL = 0; + @Key("settings.layer.live-update") + @Comment(""" + Whether to push this layer through SSE or not.""") + public static boolean LIVE_UPDATE = true; @Key("settings.layer.show-controls") @Comment(""" Whether the players layer control shows up in the layers list or not.""") diff --git a/core/src/main/java/net/pl3x/map/core/configuration/SpawnLayerConfig.java b/core/src/main/java/net/pl3x/map/core/configuration/SpawnLayerConfig.java index 86c6f7a44..edc56b5b4 100644 --- a/core/src/main/java/net/pl3x/map/core/configuration/SpawnLayerConfig.java +++ b/core/src/main/java/net/pl3x/map/core/configuration/SpawnLayerConfig.java @@ -34,9 +34,12 @@ public final class SpawnLayerConfig extends AbstractConfig { @Key("settings.layer.update-interval") @Comment(""" - How often (in seconds) to update the marker. - Setting to 0 is the same as setting it to 1.""") + How often (in seconds) to update the marker.""") public static int UPDATE_INTERVAL = 30; + @Key("settings.layer.live-update") + @Comment(""" + Whether to push this layer through SSE or not.""") + public static boolean LIVE_UPDATE = true; @Key("settings.layer.show-controls") @Comment(""" Whether the spawn layer control shows up in the layers list or not.""") diff --git a/core/src/main/java/net/pl3x/map/core/configuration/WorldBorderLayerConfig.java b/core/src/main/java/net/pl3x/map/core/configuration/WorldBorderLayerConfig.java index b7f5c78f0..9fa92e96e 100644 --- a/core/src/main/java/net/pl3x/map/core/configuration/WorldBorderLayerConfig.java +++ b/core/src/main/java/net/pl3x/map/core/configuration/WorldBorderLayerConfig.java @@ -35,9 +35,12 @@ public final class WorldBorderLayerConfig extends AbstractConfig { @Key("settings.layer.update-interval") @Comment(""" - How often (in seconds) to update the marker. - Setting to 0 is the same as setting it to 1.""") + How often (in seconds) to update the marker.""") public static int UPDATE_INTERVAL = 30; + @Key("settings.layer.live-update") + @Comment(""" + Whether to push this layer through SSE or not.""") + public static boolean LIVE_UPDATE = true; @Key("settings.layer.show-controls") @Comment(""" Whether the vanilla world border layer control shows up in the layers list or not.""") diff --git a/core/src/main/java/net/pl3x/map/core/httpd/HttpdServer.java b/core/src/main/java/net/pl3x/map/core/httpd/HttpdServer.java index 46f022a58..1423a3409 100644 --- a/core/src/main/java/net/pl3x/map/core/httpd/HttpdServer.java +++ b/core/src/main/java/net/pl3x/map/core/httpd/HttpdServer.java @@ -23,26 +23,40 @@ */ package net.pl3x.map.core.httpd; +import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.UndertowLogger; import io.undertow.UndertowOptions; +import io.undertow.server.HttpServerExchange; import io.undertow.server.handlers.resource.PathResourceManager; import io.undertow.server.handlers.resource.ResourceHandler; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.util.ETag; import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.StatusCodes; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; +import java.util.stream.Collectors; +import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.configuration.Config; import net.pl3x.map.core.configuration.Lang; import net.pl3x.map.core.log.LogFilter; import net.pl3x.map.core.log.Logger; +import net.pl3x.map.core.registry.WorldRegistry; import net.pl3x.map.core.util.FileUtil; +import net.pl3x.map.core.world.World; public class HttpdServer { + private HttpString X_ACCEL_BUFFERING = new HttpString("X-Accel-Buffering"); private Undertow server; + private LiveDataHandler liveDataHandler = new LiveDataHandler(); + + public LiveDataHandler getLiveDataHandler() { + return liveDataHandler; + } public void startServer() { if (!Config.HTTPD_ENABLED) { @@ -81,16 +95,48 @@ public void startServer() { this.server = Undertow.builder() .setServerOption(UndertowOptions.ENABLE_HTTP2, true) .addHttpListener(Config.HTTPD_PORT, Config.HTTPD_BIND) - .setHandler(exchange -> { - if (exchange.getRelativePath().startsWith("/tiles")) { - exchange.getResponseHeaders().put(Headers.CACHE_CONTROL, "max-age=0, must-revalidate, no-cache"); - } - if (exchange.getRelativePath().endsWith(".gz")) { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json"); - exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, "gzip"); - } - resourceHandler.handleRequest(exchange); - }) + .setHandler( + Handlers.path(exchange -> { + if (exchange.getRelativePath().startsWith("/tiles")) { + exchange.getResponseHeaders().put(Headers.CACHE_CONTROL, "max-age=0, must-revalidate, no-cache"); + } + if (exchange.getRelativePath().endsWith(".gz")) { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json"); + exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, "gzip"); + } + resourceHandler.handleRequest(exchange); + }) + .addPrefixPath("/sse", + Handlers.pathTemplate() + .add("{world}", exchange -> { + String worldName = exchange.getQueryParameters().get("world").peek(); + if (worldName == null || worldName.isEmpty()) { + exchange.getResponseHeaders().put(X_ACCEL_BUFFERING, "no"); + liveDataHandler.handle(exchange); + return; + } + + WorldRegistry worldRegistry = Pl3xMap.api().getWorldRegistry(); + World world = worldRegistry.get(worldName); + if (world == null || !world.isEnabled()) { + String listOfValidWorlds = worldRegistry.values().stream() + .filter(World::isEnabled) + .map(World::getName).collect(Collectors.joining(", ")); + handleError(exchange, "Could not find world named '%s'. Available worlds: %s" + .formatted(worldName, listOfValidWorlds)); + exchange.endExchange(); + return; + } + + if (exchange.isInIoThread()) { + exchange.dispatch(world.getServerSentEventHandler().get()); + } else { + exchange.getResponseHeaders().put(X_ACCEL_BUFFERING, "no"); + world.getServerSentEventHandler().handle(exchange); + } + }) + ) + ) .build(); this.server.start(); LogFilter.HIDE_UNDERTOW_LOGS = false; @@ -105,6 +151,12 @@ public void startServer() { } } + private void handleError(HttpServerExchange exchange, String errorMessage) { + exchange.setStatusCode(StatusCodes.NOT_FOUND); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json"); + exchange.getResponseSender().send("{\"error\": \"" + errorMessage + "\"}"); + } + public void stopServer() { if (!Config.HTTPD_ENABLED) { return; @@ -116,6 +168,10 @@ public void stopServer() { } LogFilter.HIDE_UNDERTOW_LOGS = true; + this.liveDataHandler.closeConnections(); + Pl3xMap.api().getWorldRegistry().forEach(world -> { + world.getServerSentEventHandler().closeConnections(); + }); this.server.stop(); LogFilter.HIDE_UNDERTOW_LOGS = false; diff --git a/core/src/main/java/net/pl3x/map/core/httpd/LiveDataHandler.java b/core/src/main/java/net/pl3x/map/core/httpd/LiveDataHandler.java new file mode 100644 index 000000000..9130ad52b --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/httpd/LiveDataHandler.java @@ -0,0 +1,152 @@ +/* + * MIT License + * + * Copyright (c) 2020-2023 William Blake Galbreath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.pl3x.map.core.httpd; + +import io.undertow.Handlers; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.sse.ServerSentEventConnection; +import io.undertow.server.handlers.sse.ServerSentEventHandler; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class LiveDataHandler { + private ServerSentEventHandler serverSentEventHandler; + + public LiveDataHandler() { + this.serverSentEventHandler = Handlers.serverSentEvents(); + } + + /** + * + * @param event The message event + * @param data The message data + * @param success The callback that is called when a message is sucessfully sent. + * @param failure The callback that is called when a message send fails. + */ + public void send(String event, String data, SuccessCallback success, FailureCallback failure) { + if (serverSentEventHandler == null) { + return; + } + + Callback callback = new Callback(success, failure); + for (ServerSentEventConnection connection : this.serverSentEventHandler.getConnections()) { + connection.send(data, event, null, callback); + } + } + + /** + * + * @param event The message event + * @param data The message data + * @param success The callback that is called when a message is sucessfully sent. + */ + public void send(String event, String data, SuccessCallback success) { + this.send(event, data, success, null); + } + + /** + * + * @param event The message event + * @param data The message data + */ + public void send(String event, String data) { + this.send(event, data, null, null); + } + + /** + * + * @param data The message data + */ + public void send(String data) { + this.send(null, data); + } + + public void closeConnections() { + for (ServerSentEventConnection connection : serverSentEventHandler.getConnections()) { + connection.shutdown(); + } + } + + public void handle(HttpServerExchange exchange) throws Exception { + this.serverSentEventHandler.handleRequest(exchange); + } + + public ServerSentEventHandler get() { + return this.serverSentEventHandler; + } + + /** + * Notification that is called when a message is sucessfully sent + */ + @FunctionalInterface + public interface SuccessCallback { + /** + * @param connection The connection + * @param data The message data + * @param event The message event + * @param id The message id + */ + void apply(@NotNull ServerSentEventConnection connection, @Nullable String data, @Nullable String event, @Nullable String id); + } + + /** + * Notification that is called when a message send fails. + */ + @FunctionalInterface + public interface FailureCallback { + /** + * @param connection The connection + * @param data The message data + * @param event The message event + * @param id The message id + * @param exception The exception + */ + void apply(@NotNull ServerSentEventConnection connection, @Nullable String data, @Nullable String event, @Nullable String id, @NotNull IOException exception); + } + + private class Callback implements ServerSentEventConnection.EventCallback { + private SuccessCallback success; + private FailureCallback failure; + + public Callback(SuccessCallback success, FailureCallback failure) { + this.success = success; + this.failure = failure; + } + + @Override + public void done(ServerSentEventConnection connection, String data, String event, String id) { + if (success != null) { + success.apply(connection, data, event, id); + } + } + + @Override + public void failed(ServerSentEventConnection connection, String data, String event, String id, IOException e) { + if (failure != null) { + failure.apply(connection, data, event, id, e); + } + } + } +} diff --git a/core/src/main/java/net/pl3x/map/core/markers/layer/Layer.java b/core/src/main/java/net/pl3x/map/core/markers/layer/Layer.java index b5f07c09b..3925fe1c4 100644 --- a/core/src/main/java/net/pl3x/map/core/markers/layer/Layer.java +++ b/core/src/main/java/net/pl3x/map/core/markers/layer/Layer.java @@ -32,6 +32,7 @@ import net.pl3x.map.core.markers.JsonSerializable; import net.pl3x.map.core.markers.marker.Marker; import net.pl3x.map.core.util.Preconditions; +import net.pl3x.map.core.util.TickUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,13 +42,14 @@ @SuppressWarnings("UnusedReturnValue") public abstract class Layer extends Keyed implements JsonSerializable { private Supplier<@NotNull String> labelSupplier; - private int updateInterval = 15; + private int updateInterval = TickUtil.toTicks(15); private boolean showControls = true; private boolean defaultHidden = false; private int priority = 99; private Integer zIndex = 99; private String pane; private String css; + private boolean liveUpdate = false; /** * Create a layer. @@ -98,7 +100,17 @@ public Layer(@NotNull String key, @NotNull Supplier<@NotNull String> labelSuppli * @return update interval */ public int getUpdateInterval() { - return this.updateInterval; + return this.getUpdateInterval(false); + } + + /** + * Get this layer's update interval (in seconds or in ticks). + * + * @param ticks set to true to get update interval as ticks instead of seconds + * @return update interval + */ + public int getUpdateInterval(boolean ticks) { + return ticks ? this.updateInterval : (int) TickUtil.toSeconds(this.updateInterval); } /** @@ -108,7 +120,19 @@ public int getUpdateInterval() { * @return this layer */ public @NotNull Layer setUpdateInterval(int updateInterval) { - this.updateInterval = updateInterval; + this.setUpdateInterval(updateInterval, false); + return this; + } + + /** + * Set this layer's update interval (in seconds or in ticks). + * + * @param updateInterval new update interval + * @param ticks set to true to treat the interval value as ticks instead of seconds + * @return this layer + */ + public @NotNull Layer setUpdateInterval(int updateInterval, boolean ticks) { + this.updateInterval = ticks ? updateInterval : TickUtil.toTicks(updateInterval); return this; } @@ -246,6 +270,26 @@ public int getPriority() { return this; } + /** + * Get if this layer gets pushed through sse. + * + * @return true if being sent through sse + */ + public @Nullable boolean isLiveUpdate() { + return this.liveUpdate; + } + + /** + * Set whether to push this layer through sse. + * + * @param liveUpdate true to push this layer through sse. + * @return this layer + */ + public @NotNull Layer setLiveUpdate(@Nullable boolean liveUpdate) { + this.liveUpdate = liveUpdate; + return this; + } + /** * Get the markers to display in this Layer. * diff --git a/core/src/main/java/net/pl3x/map/core/markers/layer/PlayersLayer.java b/core/src/main/java/net/pl3x/map/core/markers/layer/PlayersLayer.java index bf01e7e30..c4ca32573 100644 --- a/core/src/main/java/net/pl3x/map/core/markers/layer/PlayersLayer.java +++ b/core/src/main/java/net/pl3x/map/core/markers/layer/PlayersLayer.java @@ -60,6 +60,7 @@ public class PlayersLayer extends WorldLayer { public PlayersLayer(@NotNull World world) { this(KEY, world, () -> Lang.UI_LAYER_PLAYERS); setUpdateInterval(PlayersLayerConfig.UPDATE_INTERVAL); + setLiveUpdate(PlayersLayerConfig.LIVE_UPDATE); setShowControls(PlayersLayerConfig.SHOW_CONTROLS); setDefaultHidden(PlayersLayerConfig.DEFAULT_HIDDEN); setPriority(PlayersLayerConfig.PRIORITY); diff --git a/core/src/main/java/net/pl3x/map/core/markers/layer/SpawnLayer.java b/core/src/main/java/net/pl3x/map/core/markers/layer/SpawnLayer.java index 2114e589a..e27635a0c 100644 --- a/core/src/main/java/net/pl3x/map/core/markers/layer/SpawnLayer.java +++ b/core/src/main/java/net/pl3x/map/core/markers/layer/SpawnLayer.java @@ -66,6 +66,7 @@ public SpawnLayer(@NotNull World world) { } setUpdateInterval(SpawnLayerConfig.UPDATE_INTERVAL); + setLiveUpdate(SpawnLayerConfig.LIVE_UPDATE); setShowControls(SpawnLayerConfig.SHOW_CONTROLS); setDefaultHidden(SpawnLayerConfig.DEFAULT_HIDDEN); setPriority(SpawnLayerConfig.PRIORITY); diff --git a/core/src/main/java/net/pl3x/map/core/markers/layer/WorldBorderLayer.java b/core/src/main/java/net/pl3x/map/core/markers/layer/WorldBorderLayer.java index 33d8e97bd..753d27818 100644 --- a/core/src/main/java/net/pl3x/map/core/markers/layer/WorldBorderLayer.java +++ b/core/src/main/java/net/pl3x/map/core/markers/layer/WorldBorderLayer.java @@ -53,6 +53,7 @@ public class WorldBorderLayer extends WorldLayer { public WorldBorderLayer(@NotNull World world) { this(KEY, world, () -> Lang.UI_LAYER_WORLDBORDER); setUpdateInterval(WorldBorderLayerConfig.UPDATE_INTERVAL); + setLiveUpdate(WorldBorderLayerConfig.LIVE_UPDATE); setShowControls(WorldBorderLayerConfig.SHOW_CONTROLS); setDefaultHidden(WorldBorderLayerConfig.DEFAULT_HIDDEN); setPriority(WorldBorderLayerConfig.PRIORITY); diff --git a/core/src/main/java/net/pl3x/map/core/network/Network.java b/core/src/main/java/net/pl3x/map/core/network/Network.java index 5f943de4c..c6bb92eea 100644 --- a/core/src/main/java/net/pl3x/map/core/network/Network.java +++ b/core/src/main/java/net/pl3x/map/core/network/Network.java @@ -35,17 +35,7 @@ public abstract class Network { public abstract void unregister(); - public void sendServerData(T player) { - ByteArrayDataOutput out = out(); - - out.writeInt(Constants.PROTOCOL); - out.writeInt(Constants.SERVER_DATA); - out.writeInt(Constants.RESPONSE_SUCCESS); - - out.writeUTF(Config.WEB_ADDRESS); - - send(player, out); - } + protected abstract void sendServerData(T player); protected abstract void sendMapData(T player, int id); diff --git a/core/src/main/java/net/pl3x/map/core/player/PlayerRegistry.java b/core/src/main/java/net/pl3x/map/core/player/PlayerRegistry.java index 820ec23a1..7a8b032ca 100644 --- a/core/src/main/java/net/pl3x/map/core/player/PlayerRegistry.java +++ b/core/src/main/java/net/pl3x/map/core/player/PlayerRegistry.java @@ -23,10 +23,17 @@ */ package net.pl3x.map.core.player; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; +import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.configuration.PlayersLayerConfig; import net.pl3x.map.core.registry.Registry; import net.pl3x.map.core.util.Preconditions; import org.jetbrains.annotations.NotNull; @@ -89,4 +96,34 @@ public class PlayerRegistry extends Registry<@NotNull Player> { Player player = get(uuid); return player == null ? Optional.empty() : Optional.of(player); } + + public @NotNull List<@NotNull Object> parsePlayers() { + if (!PlayersLayerConfig.ENABLED) { + return Collections.emptyList(); + } + List players = new ArrayList<>(); + Pl3xMap.api().getPlayerRegistry().forEach(player -> { + // do not expose hidden players in the json + if (player.isHidden() || player.isNPC()) { + return; + } + if (PlayersLayerConfig.HIDE_SPECTATORS && player.isSpectator()) { + return; + } + if (PlayersLayerConfig.HIDE_INVISIBLE && player.isInvisible()) { + return; + } + + Map playerEntry = new LinkedHashMap<>(); + + playerEntry.put("name", player.getDecoratedName()); + playerEntry.put("uuid", player.getUUID().toString()); + playerEntry.put("displayName", player.getDecoratedName()); + playerEntry.put("world", player.getWorld().getName()); + playerEntry.put("position", player.getPosition()); + + players.add(playerEntry); + }); + return players; + } } diff --git a/core/src/main/java/net/pl3x/map/core/registry/WorldRegistry.java b/core/src/main/java/net/pl3x/map/core/registry/WorldRegistry.java index a1c58694e..732ea5e6c 100644 --- a/core/src/main/java/net/pl3x/map/core/registry/WorldRegistry.java +++ b/core/src/main/java/net/pl3x/map/core/registry/WorldRegistry.java @@ -46,6 +46,7 @@ public class WorldRegistry extends Registry<@NotNull World> { if (world != null) { Pl3xMap.api().getEventRegistry().callEvent(new WorldUnloadedEvent(world)); world.getMarkerTask().cancel(); + world.getLiveDataTask().cancel(); //world.getRegionFileWatcher().stop(); world.cleanup(); } diff --git a/core/src/main/java/net/pl3x/map/core/renderer/task/AbstractDataTask.java b/core/src/main/java/net/pl3x/map/core/renderer/task/AbstractDataTask.java new file mode 100644 index 000000000..1bfc1aa9e --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/renderer/task/AbstractDataTask.java @@ -0,0 +1,111 @@ +/* + * MIT License + * + * Copyright (c) 2020-2023 William Blake Galbreath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.pl3x.map.core.renderer.task; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.log.Logger; +import net.pl3x.map.core.markers.JsonObjectWrapper; +import net.pl3x.map.core.markers.marker.Marker; +import net.pl3x.map.core.scheduler.Task; +import net.pl3x.map.core.world.World; +import org.jetbrains.annotations.NotNull; + +public abstract class AbstractDataTask extends Task { + protected final Gson gson = new GsonBuilder() + //.setPrettyPrinting() + .disableHtmlEscaping() + .serializeNulls() + .setLenient() + .registerTypeHierarchyAdapter(Marker.class, new Adapter()) + .create(); + + protected final World world; + protected final Map<@NotNull String, @NotNull Long> lastUpdated = new HashMap<>(); + protected final ExecutorService executor; + protected final String executorName; + + protected CompletableFuture future; + protected boolean running; + + public AbstractDataTask(int delay, boolean repeat, World world, String serviceName, int threads) { + super(delay, repeat); + this.world = world; + this.executor = Pl3xMap.ThreadFactory.createService(serviceName, threads); + this.executorName = serviceName; + } + + public AbstractDataTask(int delay, boolean repeat, World world, String serviceName) { + super(delay, repeat); + this.world = world; + this.executor = Pl3xMap.ThreadFactory.createService(serviceName); + this.executorName = serviceName; + } + + @Override + public void run() { + if (this.running) { + return; + } + this.running = true; + this.future = CompletableFuture.runAsync(() -> { + try { + parse(); + } catch (Throwable t) { + Logger.severe("Failed to parse task %s for world %s".formatted(executorName, world.getName()), t); + } + this.running = false; + }, this.executor); + } + + @Override + public void cancel() { + super.cancel(); + if (this.future != null) { + this.future.cancel(true); + } + } + + public abstract void parse(); + + protected static class Adapter implements JsonSerializer<@NotNull Marker> { + @Override + public @NotNull JsonElement serialize(@NotNull Marker marker, @NotNull Type type, @NotNull JsonSerializationContext context) { + JsonObjectWrapper wrapper = new JsonObjectWrapper(); + wrapper.addProperty("type", marker.getType()); + wrapper.addProperty("data", marker); + wrapper.addProperty("options", marker.getOptions()); + return wrapper.getJsonObject(); + } + } +} diff --git a/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateLiveData.java b/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateLiveData.java new file mode 100644 index 000000000..b50f15aa3 --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateLiveData.java @@ -0,0 +1,94 @@ +/* + * MIT License + * + * Copyright (c) 2020-2023 William Blake Galbreath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.pl3x.map.core.renderer.task; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.log.Logger; +import net.pl3x.map.core.markers.layer.Layer; +import net.pl3x.map.core.markers.marker.Marker; +import net.pl3x.map.core.world.World; +import org.jetbrains.annotations.NotNull; + +public class UpdateLiveData extends AbstractDataTask { + private final Cache<@NotNull String, Integer> markerCache = CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(); + private Map> liveUpdateFutures; + + public UpdateLiveData(@NotNull World world, int threads) { + super(1, true, world, "Pl3xMap-LiveData", threads); + this.liveUpdateFutures = new HashMap<>(); + } + + @Override + public void cancel() { + super.cancel(); + this.liveUpdateFutures.forEach((key, future) -> future.cancel(true)); + this.liveUpdateFutures.clear(); + } + + @Override + public void parse() { + this.world.getLayerRegistry().entrySet().forEach(entry -> { + String key = entry.getKey(); + Layer layer = entry.getValue(); + + if (!layer.isLiveUpdate()) { + return; + } + + CompletableFuture future = liveUpdateFutures.get(key); + if (future != null && !future.isDone()) { + return; + } + + this.liveUpdateFutures.put(key, CompletableFuture.runAsync(() -> { + try { + List> list = new ArrayList<>(layer.getMarkers()); + Integer markerCacheIfPresent = markerCache.getIfPresent(key); + int markerHashCode = list.hashCode(); + if (markerCacheIfPresent == null || !markerCacheIfPresent.equals(markerHashCode)) { + Logger.debug("[%s/%s] sending through sse %d".formatted(this.world.getName(), key, (System.currentTimeMillis()))); + world.getServerSentEventHandler().send("markers", String.format("{\"key\": \"%s\", \"markers\": %s}", key, this.gson.toJson(list))); + markerCache.put(key, markerHashCode); + } + } catch (Throwable t) { + Logger.debug("[%s/%s] failed".formatted(this.world.getName(), key)); + t.printStackTrace(); + } finally { + this.liveUpdateFutures.remove(key); + } + }, this.executor)); + }); + } +} diff --git a/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateMarkerData.java b/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateMarkerData.java index 65992667b..765f5714e 100644 --- a/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateMarkerData.java +++ b/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateMarkerData.java @@ -23,75 +23,23 @@ */ package net.pl3x.map.core.renderer.task; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.log.Logger; -import net.pl3x.map.core.markers.JsonObjectWrapper; import net.pl3x.map.core.markers.layer.Layer; import net.pl3x.map.core.markers.marker.Marker; -import net.pl3x.map.core.scheduler.Task; import net.pl3x.map.core.util.FileUtil; +import net.pl3x.map.core.util.TickUtil; import net.pl3x.map.core.world.World; import org.jetbrains.annotations.NotNull; -public class UpdateMarkerData extends Task { - private final Gson gson = new GsonBuilder() - //.setPrettyPrinting() - .disableHtmlEscaping() - .serializeNulls() - .setLenient() - .registerTypeHierarchyAdapter(Marker.class, new Adapter()) - .create(); - - private final World world; - private final Map<@NotNull String, @NotNull Long> lastUpdated = new HashMap<>(); - private final ExecutorService executor; - - private CompletableFuture future; - private boolean running; - +public class UpdateMarkerData extends AbstractDataTask { public UpdateMarkerData(@NotNull World world) { - super(1, true); - this.world = world; - this.executor = Pl3xMap.ThreadFactory.createService("Pl3xMap-Markers"); + super(TickUtil.toTicks(1), true, world, "Pl3xMap-Markers"); } @Override - public void run() { - if (this.running) { - return; - } - this.running = true; - this.future = CompletableFuture.runAsync(() -> { - try { - parseLayers(); - } catch (Throwable t) { - Logger.severe("Failed to parse Layers", t); - } - this.running = false; - }, this.executor); - } - - @Override - public void cancel() { - super.cancel(); - if (this.future != null) { - this.future.cancel(true); - } - } - - private void parseLayers() { + public void parse() { List layers = new ArrayList<>(); this.world.getLayerRegistry().entrySet().forEach(entry -> { @@ -100,10 +48,10 @@ private void parseLayers() { try { layers.add(layer.toJson()); - long now = System.currentTimeMillis() / 1000; - long lastUpdate = this.lastUpdated.getOrDefault(key, 0L); + long now = System.currentTimeMillis(); + long lastUpdated = this.lastUpdated.getOrDefault(key, 0L); - if (now - lastUpdate > layer.getUpdateInterval()) { + if (now - lastUpdated > Math.max(TickUtil.toMilliseconds(layer.getUpdateInterval()), 1000)) { List> list = new ArrayList<>(layer.getMarkers()); FileUtil.writeJson(this.gson.toJson(list), this.world.getMarkersDirectory().resolve(key.replace(":", "-") + ".json")); this.lastUpdated.put(key, now); @@ -115,15 +63,4 @@ private void parseLayers() { FileUtil.writeJson(this.gson.toJson(layers), this.world.getTilesDirectory().resolve("markers.json")); } - - private static class Adapter implements JsonSerializer<@NotNull Marker> { - @Override - public @NotNull JsonElement serialize(@NotNull Marker marker, @NotNull Type type, @NotNull JsonSerializationContext context) { - JsonObjectWrapper wrapper = new JsonObjectWrapper(); - wrapper.addProperty("type", marker.getType()); - wrapper.addProperty("data", marker); - wrapper.addProperty("options", marker.getOptions()); - return wrapper.getJsonObject(); - } - } } diff --git a/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateSettingsData.java b/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateSettingsData.java index cf36ac4d8..76e4a42a7 100644 --- a/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateSettingsData.java +++ b/core/src/main/java/net/pl3x/map/core/renderer/task/UpdateSettingsData.java @@ -26,7 +26,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; @@ -34,7 +33,6 @@ import net.pl3x.map.core.Pl3xMap; import net.pl3x.map.core.configuration.Config; import net.pl3x.map.core.configuration.Lang; -import net.pl3x.map.core.configuration.PlayersLayerConfig; import net.pl3x.map.core.configuration.WorldConfig; import net.pl3x.map.core.image.io.IO; import net.pl3x.map.core.log.Logger; @@ -45,12 +43,14 @@ import org.jetbrains.annotations.NotNull; public class UpdateSettingsData extends Task { + private int fileTick; private final Gson gson = new GsonBuilder() //.setPrettyPrinting() .disableHtmlEscaping() .serializeNulls() .setLenient() .create(); + private int jsonHashCache = -1; public UpdateSettingsData() { super(1, true); @@ -65,36 +65,6 @@ public void run() { } } - private @NotNull List<@NotNull Object> parsePlayers() { - if (!PlayersLayerConfig.ENABLED) { - return Collections.emptyList(); - } - List players = new ArrayList<>(); - Pl3xMap.api().getPlayerRegistry().forEach(player -> { - // do not expose hidden players in the json - if (player.isHidden() || player.isNPC()) { - return; - } - if (PlayersLayerConfig.HIDE_SPECTATORS && player.isSpectator()) { - return; - } - if (PlayersLayerConfig.HIDE_INVISIBLE && player.isInvisible()) { - return; - } - - Map playerEntry = new LinkedHashMap<>(); - - playerEntry.put("name", player.getDecoratedName()); - playerEntry.put("uuid", player.getUUID().toString()); - playerEntry.put("displayName", player.getDecoratedName()); - playerEntry.put("world", player.getWorld().getName()); - playerEntry.put("position", player.getPosition()); - - players.add(playerEntry); - }); - return players; - } - private @NotNull List<@NotNull Map<@NotNull String, @NotNull Object>> parseWorlds() { List> worldSettings = new ArrayList<>(); Pl3xMap.api().getWorldRegistry().entrySet().forEach(entry -> { @@ -185,12 +155,22 @@ private void parseSettings() { map.put("zoom", zoom); try { - map.put("players", parsePlayers()); + map.put("players", Pl3xMap.api().getPlayerRegistry().parsePlayers()); map.put("worldSettings", parseWorlds()); } catch (Throwable t) { Logger.severe("Failed to parse players and worlds settings", t); } - FileUtil.writeJson(this.gson.toJson(map), FileUtil.getTilesDir().resolve("settings.json")); + String json = this.gson.toJson(map); + + if (jsonHashCache != json.hashCode()) { + Pl3xMap.api().getHttpdServer().getLiveDataHandler().send("settings", json); + jsonHashCache = json.hashCode(); + } + + if (fileTick++ >= 20) { + fileTick = 0; + FileUtil.writeJson(json, FileUtil.getTilesDir().resolve("settings.json")); + } } } diff --git a/core/src/main/java/net/pl3x/map/core/scheduler/Scheduler.java b/core/src/main/java/net/pl3x/map/core/scheduler/Scheduler.java index d61f42495..c7149e0cc 100644 --- a/core/src/main/java/net/pl3x/map/core/scheduler/Scheduler.java +++ b/core/src/main/java/net/pl3x/map/core/scheduler/Scheduler.java @@ -27,6 +27,7 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import net.pl3x.map.core.log.Logger; +import net.pl3x.map.core.util.TickUtil; import org.jetbrains.annotations.NotNull; public class Scheduler { @@ -35,7 +36,7 @@ public class Scheduler { private boolean ticking; /** - * Tick this scheduler once every second. + * Tick this scheduler once every tick. */ public void tick() { if (this.ticking) { @@ -61,11 +62,16 @@ public void tick() { iter.remove(); } } catch (Throwable t) { - Logger.severe("Failed to tick a task", t); + error("Failed to tick a task", t); } this.ticking = false; } + // TODO: make logger work in fabric client + protected void error(String message, Throwable t) { + Logger.severe(message, t); + } + /** * Cancel all scheduled tasks. */ @@ -96,15 +102,38 @@ public void addTask(int delay, @NotNull Runnable runnable) { addTask(delay, false, runnable); } + /** + * Add task to the scheduler. + * + * @param delay Delay (in seconds or ticks) before task starts + * @param runnable Task to add + * @param ticks Set to true to pass the delay as ticks instead of seconds + */ + public void addTask(int delay, @NotNull Runnable runnable, boolean ticks) { + addTask(delay, false, runnable, ticks); + } + /** * Add task to the scheduler. * * @param delay Delay (in seconds) before task starts - * @param repeat Delay (in seconds) before task repeats + * @param repeat Whether this task should repeat * @param runnable Task to add */ public void addTask(int delay, boolean repeat, @NotNull Runnable runnable) { - addTask(new Task(delay, repeat) { + addTask(delay, repeat, runnable, false); + } + + /** + * Add task to the scheduler. + * + * @param delay Delay (in seconds or ticks) before task starts + * @param repeat Whether this task should repeat + * @param runnable Task to add + * @param ticks Set to true to pass the delay as ticks instead of seconds + */ + public void addTask(int delay, boolean repeat, @NotNull Runnable runnable, boolean ticks) { + addTask(new Task(ticks ? delay : TickUtil.toTicks(delay), repeat) { @Override public void run() { runnable.run(); diff --git a/core/src/main/java/net/pl3x/map/core/scheduler/Task.java b/core/src/main/java/net/pl3x/map/core/scheduler/Task.java index b294e8464..b332496f4 100644 --- a/core/src/main/java/net/pl3x/map/core/scheduler/Task.java +++ b/core/src/main/java/net/pl3x/map/core/scheduler/Task.java @@ -33,7 +33,7 @@ public abstract class Task implements Runnable { /** * Creates a new schedulable task. * - * @param delay Delay (in seconds) before task starts + * @param delay Delay (in ticks) before task starts */ public Task(int delay) { this(delay, false); @@ -42,7 +42,7 @@ public Task(int delay) { /** * Creates a new schedulable task. * - * @param delay Delay (in seconds) before task starts + * @param delay Delay (in ticks) before task starts * @param repeat Whether this task should repeat */ public Task(int delay, boolean repeat) { diff --git a/core/src/main/java/net/pl3x/map/core/util/TickUtil.java b/core/src/main/java/net/pl3x/map/core/util/TickUtil.java new file mode 100644 index 000000000..3a07edd3d --- /dev/null +++ b/core/src/main/java/net/pl3x/map/core/util/TickUtil.java @@ -0,0 +1,38 @@ +/* + * MIT License + * + * Copyright (c) 2020-2023 William Blake Galbreath + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.pl3x.map.core.util; + +public class TickUtil { + public static double toSeconds(int ticks) { + return ticks * (1.0 / 20); + } + + public static int toTicks(int seconds) { + return seconds * 20; + } + + public static int toMilliseconds(int ticks) { + return ticks * 50; + } +} diff --git a/core/src/main/java/net/pl3x/map/core/world/World.java b/core/src/main/java/net/pl3x/map/core/world/World.java index 9fe36ddcc..a77213050 100644 --- a/core/src/main/java/net/pl3x/map/core/world/World.java +++ b/core/src/main/java/net/pl3x/map/core/world/World.java @@ -41,10 +41,12 @@ import javax.imageio.ImageIO; import net.pl3x.map.core.Keyed; import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.configuration.Config; import net.pl3x.map.core.configuration.PlayersLayerConfig; import net.pl3x.map.core.configuration.SpawnLayerConfig; import net.pl3x.map.core.configuration.WorldBorderLayerConfig; import net.pl3x.map.core.configuration.WorldConfig; +import net.pl3x.map.core.httpd.LiveDataHandler; import net.pl3x.map.core.image.IconImage; import net.pl3x.map.core.log.Logger; import net.pl3x.map.core.markers.Point; @@ -58,6 +60,7 @@ import net.pl3x.map.core.registry.BiomeRegistry; import net.pl3x.map.core.registry.Registry; import net.pl3x.map.core.renderer.Renderer; +import net.pl3x.map.core.renderer.task.UpdateLiveData; import net.pl3x.map.core.renderer.task.UpdateMarkerData; import net.pl3x.map.core.util.FileUtil; import net.pl3x.map.core.util.Mathf; @@ -79,6 +82,8 @@ public abstract class World extends Keyed { private final Point spawn; private final Type type; + private final LiveDataHandler liveDataHandler; + private final BiomeManager biomeManager; private final BiomeRegistry biomeRegistry; private final Registry<@NotNull Layer> layerRegistry; @@ -87,6 +92,7 @@ public abstract class World extends Keyed { private final RegionModifiedState regionModifiedState; //private final RegionFileWatcher regionFileWatcher; private final UpdateMarkerData markerTask; + private final UpdateLiveData liveDataTask; private final Map<@NotNull String, Renderer.@NotNull Builder> renderers = new LinkedHashMap<>(); public World(@NotNull String name, long seed, @NotNull Point spawn, @NotNull Type type, @NotNull Path regionDirectory) { @@ -96,6 +102,8 @@ public World(@NotNull String name, long seed, @NotNull Point spawn, @NotNull Typ this.spawn = spawn; this.type = type; + this.liveDataHandler = new LiveDataHandler(); + String safeNameForDirectories = name.replace(":", "-"); this.regionDirectory = regionDirectory; @@ -122,6 +130,7 @@ public World(@NotNull String name, long seed, @NotNull Point spawn, @NotNull Typ this.regionModifiedState = new RegionModifiedState(this); //this.regionFileWatcher = new RegionFileWatcher(this); this.markerTask = new UpdateMarkerData(this); + this.liveDataTask = new UpdateLiveData(this, Config.LIVE_UPDATE_THREADS); } protected void init() { @@ -167,7 +176,12 @@ protected void init() { Pl3xMap.api().getRegionProcessor().addRegions(this, listRegions(false)); Logger.debug("Starting marker task"); - Pl3xMap.api().getScheduler().addTask(1, true, this.markerTask); + Pl3xMap.api().getScheduler().addTask(this.markerTask); + + if (Config.LIVE_UPDATE_ENABLED) { + Logger.debug("Starting live data task"); + Pl3xMap.api().getScheduler().addTask(this.liveDataTask); + } // load up custom markers Logger.debug("Loading custom markers for " + getName()); @@ -213,6 +227,10 @@ public void cleanup() { return this.markerTask; } + public @NotNull UpdateLiveData getLiveDataTask() { + return this.liveDataTask; + } + public @NotNull Map<@NotNull String, Renderer.@NotNull Builder> getRenderers() { return Collections.unmodifiableMap(this.renderers); } @@ -251,6 +269,10 @@ public int getSkylight() { return this.type; } + public LiveDataHandler getServerSentEventHandler() { + return liveDataHandler; + } + public @NotNull BiomeManager getBiomeManager() { return this.biomeManager; } diff --git a/fabric/build.gradle b/fabric/build.gradle index a3a08a3e3..fc95b3f3c 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -23,7 +23,7 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:$fabricLoaderVersion" modImplementation "net.fabricmc.fabric-api:fabric-api:$fabricApiVersion" - modImplementation include("cloud.commandframework:cloud-fabric:$cloudVersion") + modImplementation include("org.incendo:cloud-fabric:$cloudVersion") modImplementation include("net.kyori:adventure-platform-fabric:$adventureFabricVersion") } diff --git a/fabric/src/main/java/net/pl3x/map/fabric/client/ClientScheduler.java b/fabric/src/main/java/net/pl3x/map/fabric/client/ClientScheduler.java new file mode 100644 index 000000000..e0ab0f769 --- /dev/null +++ b/fabric/src/main/java/net/pl3x/map/fabric/client/ClientScheduler.java @@ -0,0 +1,10 @@ +package net.pl3x.map.fabric.client; + +import net.pl3x.map.core.scheduler.Scheduler; + +public class ClientScheduler extends Scheduler { + @Override + protected void error(String message, Throwable t) { + Pl3xMapFabricClient.LOGGER.error(message, t); + } +} diff --git a/fabric/src/main/java/net/pl3x/map/fabric/client/Pl3xMapFabricClient.java b/fabric/src/main/java/net/pl3x/map/fabric/client/Pl3xMapFabricClient.java index 5a4ca70a7..ec9ad0cc0 100644 --- a/fabric/src/main/java/net/pl3x/map/fabric/client/Pl3xMapFabricClient.java +++ b/fabric/src/main/java/net/pl3x/map/fabric/client/Pl3xMapFabricClient.java @@ -29,23 +29,32 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.network.Constants; import net.pl3x.map.core.scheduler.Scheduler; import net.pl3x.map.fabric.client.duck.MapInstance; -import net.pl3x.map.fabric.client.manager.NetworkManager; import net.pl3x.map.fabric.client.manager.TileManager; +import net.pl3x.map.fabric.common.network.ClientboundMapPayload; +import net.pl3x.map.fabric.common.network.ClientboundServerPayload; +import net.pl3x.map.fabric.common.network.ServerboundMapPayload; +import net.pl3x.map.fabric.common.network.ServerboundServerPayload; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Pl3xMapFabricClient implements ClientModInitializer { + public static final Logger LOGGER = LoggerFactory.getLogger(Pl3xMapFabricClient.class); + private static Pl3xMapFabricClient instance; - private final NetworkManager networkManager; private final Scheduler scheduler; private final TileManager tileManager; private final ExecutorService executor = Pl3xMap.ThreadFactory.createService("Pl3xMap-Update"); @@ -62,8 +71,7 @@ public static Pl3xMapFabricClient getInstance() { public Pl3xMapFabricClient() { instance = this; - this.networkManager = new NetworkManager(this); - this.scheduler = new Scheduler(); + this.scheduler = new ClientScheduler(); this.tileManager = new TileManager(this); } @@ -76,7 +84,13 @@ public void onInitializeClient() { "pl3xmap.title" )); - getNetworkManager().initialize(); + PayloadTypeRegistry.playC2S().register(ServerboundServerPayload.TYPE, ServerboundServerPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(ClientboundServerPayload.TYPE, ClientboundServerPayload.STREAM_CODEC); + PayloadTypeRegistry.playC2S().register(ServerboundMapPayload.TYPE, ServerboundMapPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(ClientboundMapPayload.TYPE, ClientboundMapPayload.STREAM_CODEC); + + ClientPlayNetworking.registerGlobalReceiver(ClientboundServerPayload.TYPE, ClientboundServerPayload::handle); + ClientPlayNetworking.registerGlobalReceiver(ClientboundMapPayload.TYPE, ClientboundMapPayload::handle); ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { if (client.isSingleplayer()) { @@ -84,7 +98,7 @@ public void onInitializeClient() { } setEnabled(true); setIsOnServer(true); - getScheduler().addTask(0, getNetworkManager()::requestServerData); + getScheduler().addTask(0, () -> ClientPlayNetworking.send(new ServerboundServerPayload(Constants.PROTOCOL))); }); ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { @@ -97,7 +111,7 @@ public void onInitializeClient() { }); ClientTickEvents.END_CLIENT_TICK.register(client -> { - if (Minecraft.getInstance().player == null) { + if (client.player == null) { return; } while (this.keyBinding.consumeClick()) { @@ -113,10 +127,6 @@ public void onInitializeClient() { }); } - public @NotNull NetworkManager getNetworkManager() { - return this.networkManager; - } - public @NotNull Scheduler getScheduler() { return this.scheduler; } diff --git a/fabric/src/main/java/net/pl3x/map/fabric/client/manager/NetworkManager.java b/fabric/src/main/java/net/pl3x/map/fabric/client/manager/NetworkManager.java index 440ffefb6..09ef9a2a3 100644 --- a/fabric/src/main/java/net/pl3x/map/fabric/client/manager/NetworkManager.java +++ b/fabric/src/main/java/net/pl3x/map/fabric/client/manager/NetworkManager.java @@ -33,7 +33,6 @@ import net.minecraft.resources.ResourceLocation; import net.pl3x.map.core.network.Constants; import net.pl3x.map.fabric.client.Pl3xMapFabricClient; -import net.pl3x.map.fabric.client.duck.MapInstance; import org.jetbrains.annotations.NotNull; public class NetworkManager { @@ -43,87 +42,4 @@ public class NetworkManager { public NetworkManager(@NotNull Pl3xMapFabricClient mod) { this.mod = mod; } - - public void initialize() { - ClientPlayNetworking.registerGlobalReceiver(this.channel, (client, handler, buf, sender) -> { - ByteArrayDataInput packet = in(accessByteBufWithCorrectSize(buf)); - - int protocol = packet.readInt(); - if (protocol != Constants.PROTOCOL) { - this.mod.setEnabled(false); - return; - } - - int packetType = packet.readInt(); - switch (packetType) { - case Constants.SERVER_DATA -> { - int response = packet.readInt(); - if (response != Constants.RESPONSE_SUCCESS) { - this.mod.setEnabled(false); - return; - } - this.mod.getTileManager().initialize(); - this.mod.setServerUrl(packet.readUTF()); - } - case Constants.MAP_DATA -> { - int response = packet.readInt(); - switch (response) { - case Constants.ERROR_NO_SUCH_MAP, Constants.ERROR_NO_SUCH_WORLD, Constants.ERROR_NOT_VANILLA_MAP -> { - MapInstance texture = (MapInstance) Minecraft.getInstance().gameRenderer.getMapRenderer().maps.get(packet.readInt()); - if (texture != null) { - texture.skip(); - } - } - case Constants.RESPONSE_SUCCESS -> { - MapInstance texture = (MapInstance) Minecraft.getInstance().gameRenderer.getMapRenderer().maps.get(packet.readInt()); - if (texture != null) { - texture.setData(packet.readByte(), packet.readInt(), packet.readInt(), packet.readUTF()); - } - } - } - } - } - }); - } - - public void requestServerData() { - ByteArrayDataOutput out = out(); - out.writeInt(Constants.PROTOCOL); - out.writeInt(Constants.SERVER_DATA); - sendPacket(out); - } - - public void requestMapData(int id) { - ByteArrayDataOutput out = out(); - out.writeInt(Constants.PROTOCOL); - out.writeInt(Constants.MAP_DATA); - out.writeInt(id); - sendPacket(out); - } - - private void sendPacket(@NotNull ByteArrayDataOutput packet) { - if (Minecraft.getInstance().getConnection() == null) { - // not in game yet; reschedule - this.mod.getScheduler().addTask(0, () -> sendPacket(packet)); - return; - } - ClientPlayNetworking.send(this.channel, new FriendlyByteBuf(Unpooled.wrappedBuffer(packet.toByteArray()))); - } - - public static byte[] accessByteBufWithCorrectSize(FriendlyByteBuf buf) { - int i = buf.writerIndex(); - byte[] bytes = new byte[i]; - buf.getBytes(0, bytes); - return bytes; - } - - @SuppressWarnings("UnstableApiUsage") - private @NotNull ByteArrayDataOutput out() { - return ByteStreams.newDataOutput(); - } - - @SuppressWarnings("UnstableApiUsage") - private @NotNull ByteArrayDataInput in(byte[] bytes) { - return ByteStreams.newDataInput(bytes); - } } diff --git a/fabric/src/main/java/net/pl3x/map/fabric/client/manager/TileManager.java b/fabric/src/main/java/net/pl3x/map/fabric/client/manager/TileManager.java index 80dcb1126..6e5a1c8cb 100644 --- a/fabric/src/main/java/net/pl3x/map/fabric/client/manager/TileManager.java +++ b/fabric/src/main/java/net/pl3x/map/fabric/client/manager/TileManager.java @@ -37,6 +37,7 @@ import javax.imageio.ImageIO; import net.pl3x.map.core.scheduler.Task; import net.pl3x.map.core.util.Mathf; +import net.pl3x.map.core.util.TickUtil; import net.pl3x.map.fabric.client.Pl3xMapFabricClient; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; @@ -64,7 +65,7 @@ public void initialize() { // update once next tick this.mod.getScheduler().addTask(0, this::update); // setup repeating task to update every 5 seconds - this.task = new Task(5, true) { + this.task = new Task(TickUtil.toTicks(5), true) { @Override public void run() { update(); @@ -122,7 +123,7 @@ private Loader(@NotNull Pl3xMapFabricClient mod, @NotNull String world) { this.mod.getServerUrl(), this.world, 0, - "basic", + "vintage_story", Mathf.longToX(region), Mathf.longToZ(region) ); diff --git a/fabric/src/main/java/net/pl3x/map/fabric/client/mixin/MapInstanceMixin.java b/fabric/src/main/java/net/pl3x/map/fabric/client/mixin/MapInstanceMixin.java index c4bb18a8d..2d1ac23bb 100644 --- a/fabric/src/main/java/net/pl3x/map/fabric/client/mixin/MapInstanceMixin.java +++ b/fabric/src/main/java/net/pl3x/map/fabric/client/mixin/MapInstanceMixin.java @@ -26,6 +26,7 @@ import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; import java.awt.image.BufferedImage; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.minecraft.client.gui.MapRenderer; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.world.level.material.MapColor; @@ -33,6 +34,7 @@ import net.pl3x.map.core.util.Colors; import net.pl3x.map.fabric.client.Pl3xMapFabricClient; import net.pl3x.map.fabric.client.duck.MapInstance; +import net.pl3x.map.fabric.common.network.ServerboundMapPayload; import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -94,7 +96,7 @@ private void updateTexture(@NotNull CallbackInfo ci) { // ask for data from server if we haven't already if (!this.isReady) { - this.mod.getNetworkManager().requestMapData(this.id); + ClientPlayNetworking.send(new ServerboundMapPayload(this.id)); this.skip = true; return; } diff --git a/fabric/src/main/java/net/pl3x/map/fabric/common/network/ClientboundMapPayload.java b/fabric/src/main/java/net/pl3x/map/fabric/common/network/ClientboundMapPayload.java new file mode 100644 index 000000000..452136eb8 --- /dev/null +++ b/fabric/src/main/java/net/pl3x/map/fabric/common/network/ClientboundMapPayload.java @@ -0,0 +1,62 @@ +package net.pl3x.map.fabric.common.network; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.client.Minecraft; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.pl3x.map.core.network.Constants; +import net.pl3x.map.fabric.client.Pl3xMapFabricClient; +import net.pl3x.map.fabric.client.duck.MapInstance; + +public record ClientboundMapPayload(int protocol, int response, int mapId, byte scale, int centerX, int centerZ, String worldName) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundMapPayload::write, ClientboundMapPayload::new); + public static final Type TYPE = new Type<>(new ResourceLocation(Constants.MODID, "client_map_data")); + + public ClientboundMapPayload(int protocol, int response, int mapId) { + this(protocol, response, mapId, (byte) 0, 0, 0, null); + } + + public ClientboundMapPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readInt(), friendlyByteBuf.readInt(), friendlyByteBuf.readInt(), friendlyByteBuf.readByte(), friendlyByteBuf.readInt(), friendlyByteBuf.readInt(), friendlyByteBuf.readUtf()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeInt(protocol); + friendlyByteBuf.writeInt(response); + friendlyByteBuf.writeInt(mapId); + friendlyByteBuf.writeByte(scale); + friendlyByteBuf.writeInt(centerX); + friendlyByteBuf.writeInt(centerZ); + friendlyByteBuf.writeUtf(worldName); + } + + @Override + public Type type() { + return TYPE; + } + + public static void handle(ClientboundMapPayload payload, ClientPlayNetworking.Context context) { + Pl3xMapFabricClient instance = Pl3xMapFabricClient.getInstance(); + if (payload.protocol != Constants.PROTOCOL) { + instance.setEnabled(false); + return; + } + + switch (payload.response) { + case Constants.ERROR_NO_SUCH_MAP, Constants.ERROR_NO_SUCH_WORLD, Constants.ERROR_NOT_VANILLA_MAP -> { + MapInstance texture = (MapInstance) Minecraft.getInstance().gameRenderer.getMapRenderer().maps.get(payload.mapId); + if (texture != null) { + texture.skip(); + } + } + case Constants.RESPONSE_SUCCESS -> { + MapInstance texture = (MapInstance) Minecraft.getInstance().gameRenderer.getMapRenderer().maps.get(payload.mapId); + if (texture != null) { + texture.setData(payload.scale, payload.centerX, payload.centerZ, payload.worldName); + } + } + } + } +} diff --git a/fabric/src/main/java/net/pl3x/map/fabric/common/network/ClientboundServerPayload.java b/fabric/src/main/java/net/pl3x/map/fabric/common/network/ClientboundServerPayload.java new file mode 100644 index 000000000..950803bb6 --- /dev/null +++ b/fabric/src/main/java/net/pl3x/map/fabric/common/network/ClientboundServerPayload.java @@ -0,0 +1,44 @@ +package net.pl3x.map.fabric.common.network; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.pl3x.map.core.network.Constants; +import net.pl3x.map.fabric.client.Pl3xMapFabricClient; + +public record ClientboundServerPayload(int protocol, int response, String webAddress) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundServerPayload::write, ClientboundServerPayload::new); + public static final Type TYPE = new Type<>(new ResourceLocation(Constants.MODID, "client_server_data")); + + public ClientboundServerPayload(int protocol, int response) { + this(protocol, response, null); + } + + public ClientboundServerPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readInt(), friendlyByteBuf.readInt(), friendlyByteBuf.readUtf()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeInt(protocol); + friendlyByteBuf.writeInt(response); + friendlyByteBuf.writeUtf(webAddress); + } + + @Override + public Type type() { + return TYPE; + } + + public static void handle(ClientboundServerPayload payload, ClientPlayNetworking.Context context) { + Pl3xMapFabricClient instance = Pl3xMapFabricClient.getInstance(); + if (payload.protocol != Constants.PROTOCOL || payload.response != Constants.RESPONSE_SUCCESS) { + instance.setEnabled(false); + return; + } + + instance.getTileManager().initialize(); + instance.setServerUrl(payload.webAddress); + } +} diff --git a/fabric/src/main/java/net/pl3x/map/fabric/common/network/ServerboundMapPayload.java b/fabric/src/main/java/net/pl3x/map/fabric/common/network/ServerboundMapPayload.java new file mode 100644 index 000000000..888367134 --- /dev/null +++ b/fabric/src/main/java/net/pl3x/map/fabric/common/network/ServerboundMapPayload.java @@ -0,0 +1,30 @@ +package net.pl3x.map.fabric.common.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.pl3x.map.core.network.Constants; + +public record ServerboundMapPayload(int protocol, int mapId) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundMapPayload::write, ServerboundMapPayload::new); + public static final Type TYPE = new Type<>(new ResourceLocation(Constants.MODID, "server_map_data")); + + public ServerboundMapPayload(int mapId) { + this(Constants.PROTOCOL, mapId); + } + + public ServerboundMapPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readInt(), friendlyByteBuf.readInt()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeInt(protocol); + friendlyByteBuf.writeInt(mapId); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/fabric/src/main/java/net/pl3x/map/fabric/common/network/ServerboundServerPayload.java b/fabric/src/main/java/net/pl3x/map/fabric/common/network/ServerboundServerPayload.java new file mode 100644 index 000000000..c1ea5bf2c --- /dev/null +++ b/fabric/src/main/java/net/pl3x/map/fabric/common/network/ServerboundServerPayload.java @@ -0,0 +1,25 @@ +package net.pl3x.map.fabric.common.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.pl3x.map.core.network.Constants; + +public record ServerboundServerPayload(int protocol) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundServerPayload::write, ServerboundServerPayload::new); + public static final Type TYPE = new Type<>(new ResourceLocation(Constants.MODID, "server_server_data")); + + public ServerboundServerPayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readInt()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeInt(protocol); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/fabric/src/main/java/net/pl3x/map/fabric/server/FabricNetwork.java b/fabric/src/main/java/net/pl3x/map/fabric/server/FabricNetwork.java index 246b09650..54cbdc665 100644 --- a/fabric/src/main/java/net/pl3x/map/fabric/server/FabricNetwork.java +++ b/fabric/src/main/java/net/pl3x/map/fabric/server/FabricNetwork.java @@ -23,101 +23,90 @@ */ package net.pl3x.map.fabric.server; -import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteArrayDataOutput; -import io.netty.buffer.Unpooled; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.MapItem; import net.minecraft.world.level.Level; +import net.minecraft.world.level.saveddata.maps.MapId; import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.pl3x.map.core.configuration.Config; import net.pl3x.map.core.network.Constants; import net.pl3x.map.core.network.Network; -import net.pl3x.map.fabric.client.manager.NetworkManager; -import org.jetbrains.annotations.NotNull; +import net.pl3x.map.fabric.common.network.ClientboundMapPayload; +import net.pl3x.map.fabric.common.network.ClientboundServerPayload; +import net.pl3x.map.fabric.common.network.ServerboundMapPayload; +import net.pl3x.map.fabric.common.network.ServerboundServerPayload; public class FabricNetwork extends Network { private final Pl3xMapFabricServer mod; - private final ResourceLocation channel; public FabricNetwork(Pl3xMapFabricServer mod) { this.mod = mod; - this.channel = new ResourceLocation(Network.CHANNEL); } @Override public void register() { - ServerPlayNetworking.registerGlobalReceiver(this.channel, (server, player, listener, byteBuf, sender) -> { - ByteArrayDataInput in = in(NetworkManager.accessByteBufWithCorrectSize(byteBuf)); - int action = in.readInt(); - switch (action) { - case Constants.SERVER_DATA -> sendServerData(player); - case Constants.MAP_DATA -> sendMapData(player, in.readInt()); - } + PayloadTypeRegistry.playC2S().register(ServerboundServerPayload.TYPE, ServerboundServerPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(ClientboundServerPayload.TYPE, ClientboundServerPayload.STREAM_CODEC); + PayloadTypeRegistry.playC2S().register(ServerboundMapPayload.TYPE, ServerboundMapPayload.STREAM_CODEC); + PayloadTypeRegistry.playS2C().register(ClientboundMapPayload.TYPE, ClientboundMapPayload.STREAM_CODEC); + + ServerPlayNetworking.registerGlobalReceiver(ServerboundServerPayload.TYPE, (payload, context) -> { + ServerPlayNetworking.send(context.player(), new ClientboundServerPayload(Constants.PROTOCOL, Constants.RESPONSE_SUCCESS, Config.WEB_ADDRESS)); + }); + + ServerPlayNetworking.registerGlobalReceiver(ServerboundMapPayload.TYPE, (payload, context) -> { + sendMapData(context.player(), payload.mapId()); }); } @Override public void unregister() { - ServerPlayNetworking.unregisterGlobalReceiver(this.channel); + ServerPlayNetworking.unregisterGlobalReceiver(ServerboundServerPayload.TYPE.id()); + ServerPlayNetworking.unregisterGlobalReceiver(ServerboundMapPayload.TYPE.id()); + } + + @Override + protected void sendServerData(T player) { + } @Override protected void sendMapData(T player, int id) { - ByteArrayDataOutput out = out(); - out.writeInt(Constants.PROTOCOL); - out.writeInt(Constants.MAP_DATA); - out.writeInt(Constants.RESPONSE_SUCCESS); + } + + @Override + protected void send(T player, ByteArrayDataOutput out) { + } + + protected void sendMapData(ServerPlayer player, int id) { MinecraftServer server = this.mod.getServer(); if (server == null) { return; } @SuppressWarnings("DataFlowIssue") - MapItemSavedData map = MapItem.getSavedData(id, server.getLevel(Level.OVERWORLD)); + MapItemSavedData map = MapItem.getSavedData(new MapId(id), server.getLevel(Level.OVERWORLD)); if (map == null) { - out.writeInt(Constants.ERROR_NO_SUCH_MAP); - out.writeInt(id); + ServerPlayNetworking.send(player, new ClientboundMapPayload(Constants.PROTOCOL, Constants.ERROR_NO_SUCH_MAP, id)); return; } ServerLevel level = this.mod.getServer().getLevel(map.dimension); if (level == null) { - out.writeInt(Constants.ERROR_NO_SUCH_WORLD); - out.writeInt(id); + ServerPlayNetworking.send(player, new ClientboundMapPayload(Constants.PROTOCOL, Constants.ERROR_NO_SUCH_WORLD, id)); return; } - out.writeInt(id); - out.writeByte(map.scale); - out.writeInt(map.centerX); - out.writeInt(map.centerZ); - out.writeUTF(level.dimension().location().toString()); - - send(player, out); - } - - @Override - protected void send(T player, ByteArrayDataOutput out) { - FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(out.toByteArray())); - ((ServerPlayer) player).connection.send(new ClientboundCustomPayloadPacket(new CustomPacketPayload() { - @Override - public void write(@NotNull FriendlyByteBuf buf) { - buf.writeBytes(byteBuf); - } - - @Override - public @NotNull ResourceLocation id() { - return channel; - } - })); + ServerPlayNetworking.send(player, new ClientboundMapPayload( + Constants.PROTOCOL, Constants.RESPONSE_SUCCESS, id, + map.scale, map.centerX, map.centerZ, level.dimension().location().toString().replace(":", "-") + )); } } diff --git a/fabric/src/main/java/net/pl3x/map/fabric/server/FabricWorld.java b/fabric/src/main/java/net/pl3x/map/fabric/server/FabricWorld.java index 02b78dbf9..69ec44701 100644 --- a/fabric/src/main/java/net/pl3x/map/fabric/server/FabricWorld.java +++ b/fabric/src/main/java/net/pl3x/map/fabric/server/FabricWorld.java @@ -52,7 +52,7 @@ public FabricWorld(@NotNull ServerLevel level, @NotNull String name) { super( name, level.getSeed(), - Point.of(level.getLevelData().getXSpawn(), level.getLevelData().getZSpawn()), + Point.of(level.getLevelData().getSpawnPos().getX(), level.getLevelData().getSpawnPos().getZ()), Type.get(level.dimension().location().toString()), level.getChunkSource().getDataStorage().dataFolder.toPath().getParent().resolve("region") ); diff --git a/fabric/src/main/java/net/pl3x/map/fabric/server/Pl3xMapFabricServer.java b/fabric/src/main/java/net/pl3x/map/fabric/server/Pl3xMapFabricServer.java index 393a56ab6..dc67b2d1c 100644 --- a/fabric/src/main/java/net/pl3x/map/fabric/server/Pl3xMapFabricServer.java +++ b/fabric/src/main/java/net/pl3x/map/fabric/server/Pl3xMapFabricServer.java @@ -76,7 +76,6 @@ public class Pl3xMapFabricServer extends Pl3xMap implements DedicatedServerModIn private FabricServerAudiences adventure; private boolean firstTick = true; - private int tick; private FabricNetwork network; @@ -97,10 +96,7 @@ public void onInitializeServer() { Pl3xMap.api().getEventRegistry().callEvent(new ServerLoadedEvent()); this.firstTick = false; } - if (this.tick++ >= 20) { - this.tick = 0; - getScheduler().tick(); - } + getScheduler().tick(); }); ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { diff --git a/fabric/src/main/java/net/pl3x/map/fabric/server/command/FabricCommandManager.java b/fabric/src/main/java/net/pl3x/map/fabric/server/command/FabricCommandManager.java index b8fcc3bb7..245311b60 100644 --- a/fabric/src/main/java/net/pl3x/map/fabric/server/command/FabricCommandManager.java +++ b/fabric/src/main/java/net/pl3x/map/fabric/server/command/FabricCommandManager.java @@ -23,14 +23,17 @@ */ package net.pl3x.map.fabric.server.command; -import cloud.commandframework.Command; -import cloud.commandframework.execution.CommandExecutionCoordinator; -import cloud.commandframework.fabric.FabricServerCommandManager; import io.leangen.geantyref.TypeToken; import net.minecraft.commands.arguments.DimensionArgument; import net.pl3x.map.core.command.CommandHandler; import net.pl3x.map.core.command.Sender; -import net.pl3x.map.core.command.argument.parser.WorldParser; +import net.pl3x.map.core.command.parser.PlatformParsers; +import net.pl3x.map.core.command.parser.WorldParser; +import org.incendo.cloud.Command; +import org.incendo.cloud.SenderMapper; +import org.incendo.cloud.brigadier.CloudBrigadierManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.fabric.FabricServerCommandManager; import org.jetbrains.annotations.NotNull; public class FabricCommandManager implements CommandHandler { @@ -38,9 +41,12 @@ public class FabricCommandManager implements CommandHandler { private final Command.Builder<@NotNull Sender> root; public FabricCommandManager() { - this.manager = new FabricServerCommandManager<>(CommandExecutionCoordinator.simpleCoordinator(), FabricSender::create, Sender::getSender); + this.manager = new FabricServerCommandManager( + ExecutionCoordinator.simpleCoordinator(), + SenderMapper.create(FabricSender::create, Sender::getSender) + ); - var brigadier = getManager().brigadierManager(); + CloudBrigadierManager<@NotNull Sender, ?> brigadier = getManager().brigadierManager(); brigadier.setNativeNumberSuggestions(false); brigadier.registerMapping(new TypeToken>() { }, builder -> builder.toConstant(DimensionArgument.dimension()).cloudSuggestions()); @@ -57,6 +63,11 @@ public FabricCommandManager() { return this.manager; } + @Override + public @NotNull PlatformParsers getPlatformParsers() { + return new FabricParsers(); + } + @Override public Command.@NotNull Builder<@NotNull Sender> getRoot() { return this.root; diff --git a/fabric/src/main/java/net/pl3x/map/fabric/server/command/FabricParsers.java b/fabric/src/main/java/net/pl3x/map/fabric/server/command/FabricParsers.java new file mode 100644 index 000000000..7fbbe955c --- /dev/null +++ b/fabric/src/main/java/net/pl3x/map/fabric/server/command/FabricParsers.java @@ -0,0 +1,53 @@ +package net.pl3x.map.fabric.server.command; + +import net.minecraft.world.phys.Vec3; +import net.pl3x.map.core.Pl3xMap; +import net.pl3x.map.core.command.Sender; +import net.pl3x.map.core.command.parser.PlatformParsers; +import net.pl3x.map.core.markers.Point; +import net.pl3x.map.core.player.Player; +import net.pl3x.map.fabric.server.FabricPlayer; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.minecraft.modded.data.Coordinates; +import org.incendo.cloud.minecraft.modded.data.SinglePlayerSelector; +import org.incendo.cloud.minecraft.modded.parser.VanillaArgumentParsers; +import org.incendo.cloud.parser.ParserDescriptor; +import org.jetbrains.annotations.Nullable; + +public class FabricParsers implements PlatformParsers { + @Override + public ParserDescriptor columnPosParser() { + return VanillaArgumentParsers.columnPosParser(); + } + + @Override + public Point resolvePointFromColumnPos(String name, CommandContext context) { + Coordinates.ColumnCoordinates columnCoordinates = context.getOrDefault(name, null); + if (columnCoordinates == null) { + return Point.ZERO; + } + Vec3 position = columnCoordinates.position(); + return Point.of(position.x, position.z); + } + + @Override + public ParserDescriptor playerSelectorParser() { + return VanillaArgumentParsers.singlePlayerSelectorParser(); + } + + @Override + public Player resolvePlayerFromPlayerSelector(String name, CommandContext context) { + Sender sender = context.sender(); + SinglePlayerSelector playerSelector = context.getOrDefault(name, null); + if (playerSelector == null) { + if (sender instanceof Sender.Player senderPlayer) { + Player player = Pl3xMap.api().getPlayerRegistry().get(senderPlayer.getUUID()); + if (player != null) { + return player; + } + } + return null; + } + return new FabricPlayer(playerSelector.single()); + } +} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 4adcceb9b..0a0c40342 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -40,7 +40,7 @@ "fabricloader": ">=${fabricLoaderVersion}", "fabric": ">=${fabricApiVersion}", "minecraft": "~${minecraftVersion}", - "java": ">=17" + "java": ">=21" }, "recommends": { "better-fabric-console": "*" diff --git a/fabric/src/main/resources/pl3xmap.client.mixins.json b/fabric/src/main/resources/pl3xmap.client.mixins.json index 66cf3599a..b10576c90 100644 --- a/fabric/src/main/resources/pl3xmap.client.mixins.json +++ b/fabric/src/main/resources/pl3xmap.client.mixins.json @@ -1,7 +1,7 @@ { "required": true, "package": "net.pl3x.map.fabric.client.mixin", - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "refmap": "pl3xmap.refmap.json", "injectors": { "defaultRequire": 1 diff --git a/fabric/src/main/resources/pl3xmap.server.mixins.json b/fabric/src/main/resources/pl3xmap.server.mixins.json index bffe98969..4139627d4 100644 --- a/fabric/src/main/resources/pl3xmap.server.mixins.json +++ b/fabric/src/main/resources/pl3xmap.server.mixins.json @@ -1,7 +1,7 @@ { "required": true, "package": "net.pl3x.map.fabric.server.mixin", - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "refmap": "pl3xmap.refmap.json", "injectors": { "defaultRequire": 1 diff --git a/gradle.properties b/gradle.properties index 0e7b8960a..6c2c26a40 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,26 +4,26 @@ org.gradle.parallel=false systemProp.net.minecraftforge.gradle.check.gradle=false -bukkitVersion=1.20.4-R0.1-SNAPSHOT +bukkitVersion=1.20.6-R0.1-SNAPSHOT -fabricApiVersion=0.91.3+1.20.4 -fabricLoaderVersion=0.15.3 -fabricLoomVersion=1.5-SNAPSHOT +fabricApiVersion=0.97.8+1.20.6 +fabricLoaderVersion=0.15.10 +fabricLoomVersion=1.6-SNAPSHOT forgeVersion=1.20.2-48.0.6 forgeGradleVersion=[6.0,6.2) forgeLoaderVersion=[48,) -minecraftVersion=1.20.4 -paperweightVersion=1.5.11 +minecraftVersion=1.20.6 +paperweightVersion=1.6.0 shadowJarVersion=8.1.1 -adventureVersion=4.16.0 -adventureBukkitVersion=4.3.2 -adventureFabricVersion=5.12.0 +adventureVersion=4.17.0-SNAPSHOT +adventureBukkitVersion=4.3.3-SNAPSHOT +adventureFabricVersion=5.13.0-SNAPSHOT caffeineVersion=3.1.8 -cloudVersion=1.8.4 +cloudVersion=2.0.0-SNAPSHOT gsonVersion=2.10.1 guavaVersion=33.0.0-jre log4jVersion=2.14.1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e0930..a80b22ce5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/webmap/src/Pl3xMap.ts b/webmap/src/Pl3xMap.ts index 0a69dd996..76253c292 100644 --- a/webmap/src/Pl3xMap.ts +++ b/webmap/src/Pl3xMap.ts @@ -6,6 +6,7 @@ import {getJSON} from "./util/Util"; import SidebarControl from "./control/SidebarControl"; import Pl3xMapLeafletMap from "./map/Pl3xMapLeafletMap"; import "./scss/styles.scss"; +import {Player} from "./player/Player"; window.onload = function (): void { window.pl3xmap = new Pl3xMap(); @@ -23,15 +24,24 @@ export class Pl3xMap { private readonly _playerManager: PlayerManager; private readonly _worldManager: WorldManager; + private _eventSource?: EventSource; + private _langPalette: Map = new Map(); private _settings?: Settings; + private _timestamp: number = (new Date()).getTime(); private _timer: NodeJS.Timeout | undefined; constructor() { Pl3xMap._instance = this; this._map = new Pl3xMapLeafletMap(this); + + window.addEventListener('beforeunload', function () { + if (Pl3xMap.instance.eventSource != undefined) { + Pl3xMap.instance.eventSource.close(); + } + }); this._controlManager = new ControlManager(this); this._playerManager = new PlayerManager(this); @@ -50,6 +60,7 @@ export class Pl3xMap { }); this.controlManager.sidebarControl = new SidebarControl(this); const promise: Promise = this.worldManager.init(this._settings); + this._eventSource = this.initSSE(); this.update(); return promise; }); @@ -60,14 +71,33 @@ export class Pl3xMap { } private update(): void { - getJSON('tiles/settings.json').then((json): void => { + if (this._eventSource?.readyState === EventSource.OPEN && (new Date()).getTime() - this._timestamp < 1000) { + this._timer = setTimeout(() => this.update(), 1000); + return; + } + getJSON('tiles/settings.json').then( (json): void => { this._settings = json as Settings; - this.playerManager.update(this._settings); + + this.playerManager.update(this._settings.players); this._timer = setTimeout(() => this.update(), 1000); }); } + private initSSE(): EventSource { + const eventSource = new EventSource("sse"); + + eventSource.addEventListener("settings", (ev: Event) => { + this._timestamp = (new Date()).getTime(); + const messageEvent = (ev as MessageEvent); + const json: any = JSON.parse(messageEvent.data); + this._settings = json as Settings; + this.playerManager.update(this._settings.players); + }); + + return eventSource; + } + get map(): Pl3xMapLeafletMap { return this._map; } @@ -84,6 +114,10 @@ export class Pl3xMap { return this._worldManager; } + get eventSource(): EventSource | undefined { + return this._eventSource; + } + get langPalette(): Map { return this._langPalette; } @@ -91,4 +125,8 @@ export class Pl3xMap { get settings(): Settings | undefined { return this._settings; } + + get timestamp(): number { + return this._timestamp; + } } diff --git a/webmap/src/control/LinkControl.ts b/webmap/src/control/LinkControl.ts index dbed4f04e..1f912c142 100644 --- a/webmap/src/control/LinkControl.ts +++ b/webmap/src/control/LinkControl.ts @@ -5,11 +5,16 @@ import {createSVGIcon, toPoint} from "../util/Util"; import Pl3xMapLeafletMap from "../map/Pl3xMapLeafletMap"; import '../svg/link.svg'; import {World} from "../world/World"; +import {Player} from "../player/Player"; export class LinkControl extends ControlBox { private readonly _dom: HTMLAnchorElement; + private _disabled: boolean; private onEvent = (): void => { + if (this._disabled) { + return; + } this.update(); } @@ -17,6 +22,10 @@ export class LinkControl extends ControlBox { super(pl3xmap, position); this._dom = L.DomUtil.create('a', 'leaflet-control leaflet-control-button leaflet-control-link'); this._dom.appendChild(createSVGIcon('link')); + this._disabled = false; + addEventListener('followplayer', (e: CustomEvent): void => { + this._disabled = !(e.detail == undefined); // e.detail returns "null" even though it's being set to "undefined" + }); } onAdd(map: Pl3xMapLeafletMap): HTMLAnchorElement { diff --git a/webmap/src/layergroup/MarkerLayer.ts b/webmap/src/layergroup/MarkerLayer.ts index 40b2adcef..f5f82cc0c 100644 --- a/webmap/src/layergroup/MarkerLayer.ts +++ b/webmap/src/layergroup/MarkerLayer.ts @@ -47,6 +47,7 @@ export class MarkerLayer extends L.LayerGroup { private readonly _markers: Map = new Map(); + private _timestamp: number = (new Date()).getTime(); private _timer: NodeJS.Timeout | undefined; constructor(key: string, label: string, interval: number, showControls: boolean, defaultHidden: boolean, priority: number, zIndex: number, pane: string, css: string) { @@ -114,46 +115,68 @@ export class MarkerLayer extends L.LayerGroup { return this._css; } - update(world: World): void { + get timestamp(): number { + return this._timestamp; + } + + set timestamp(timestamp: number) { + this._timestamp = timestamp; + } + + update(world: World, updateOverride?: boolean): void { + if (updateOverride === undefined) { + const time = (new Date()).getTime() - this._timestamp; + // console.log(world.eventSource?.readyState); + // console.log(`${this._key}: world.eventSource?.readyState === EventSource.OPEN is ${world.eventSource?.readyState === EventSource.OPEN}`); + // console.log(`${this._key}: time (${time}) < 1000 is ${time < 1000}`); + if (world.eventSource?.readyState === EventSource.OPEN && time < 1000) { + // console.log(this._key + ": source is open and timestamp is " + time + " which is less than 1000"); + return; + } + } + getJSON(`tiles/${world.name}/markers/${this._key}.json`) - .then((json): void => { - //this.clearLayers(); // do not just clear markers, remove the ones that are missing - const toRemove: Set = new Set(this._markers.keys()); - - for (const index in Object.keys(json)) { - const existing: Marker | undefined = this._markers.get(json[index].data.key); - if (existing) { - // update - const data = json[index]; - const options: MarkerOptions | undefined = isset(data.options) ? new MarkerOptions(data.options) : undefined; - existing.update(data.data, options); - // do not remove this marker - toRemove.delete(existing.key); - } else { - // new marker - const marker: Marker | undefined = this.parseMarker(json[index]); - if (marker) { - this._markers.set(marker.key, marker); - marker.marker.addTo(this); - // inform the events - fireCustomEvent('markeradded', marker); - } - } + .then((json): void => this.updateMarkers(json, world)); + } + + updateMarkers(json: any, world: World): void { + // console.log(`updating ${this._key} with update interval of ${this._updateInterval}`); + //this.clearLayers(); // do not just clear markers, remove the ones that are missing + const toRemove: Set = new Set(this._markers.keys()); + + for (const index in Object.keys(json)) { + const existing: Marker | undefined = this._markers.get(json[index].data.key); + if (existing) { + // update + const data = json[index]; + const options: MarkerOptions | undefined = isset(data.options) ? new MarkerOptions(data.options) : undefined; + existing.update(data.data, options); + // do not remove this marker + toRemove.delete(existing.key); + } else { + // new marker + const marker: Marker | undefined = this.parseMarker(json[index]); + if (marker) { + this._markers.set(marker.key, marker); + marker.marker.addTo(this); + // inform the events + fireCustomEvent('markeradded', marker); } + } + } + + toRemove.forEach((key: string): void => { + // remove players not in updated settings file + const marker: Marker | undefined = this._markers.get(key); + if (marker) { + this._markers.delete(key); + marker.marker.remove(); + this.removeLayer(marker.marker); + fireCustomEvent('markerremoved', marker); + } + }); - toRemove.forEach((key: string): void => { - // remove players not in updated settings file - const marker: Marker | undefined = this._markers.get(key); - if (marker) { - this._markers.delete(key); - marker.marker.remove(); - this.removeLayer(marker.marker); - fireCustomEvent('markerremoved', marker); - } - }); - - this._timer = setTimeout(() => this.update(world), this._updateInterval); - }); + this._timer = setTimeout(() => this.update(world), this._updateInterval); } unload(): void { diff --git a/webmap/src/player/PlayerManager.ts b/webmap/src/player/PlayerManager.ts index 7e2ddfd77..773d0c4f0 100644 --- a/webmap/src/player/PlayerManager.ts +++ b/webmap/src/player/PlayerManager.ts @@ -1,6 +1,5 @@ import {Pl3xMap} from "../Pl3xMap"; import {Player} from "./Player"; -import {Settings} from "../settings/Settings"; import {fireCustomEvent, toCenteredLatLng} from "../util/Util"; import {WorldManager} from "../world/WorldManager"; import Pl3xMapLeafletMap from "../map/Pl3xMapLeafletMap"; @@ -17,10 +16,10 @@ export class PlayerManager { this._pl3xmap = pl3xmap; } - public update(settings: Settings): void { + public update(players: Player[]): void { const toRemove: Set = new Set(this._players.keys()); - settings.players.forEach((data: Player): void => { + players.forEach((data: Player): void => { const existing: Player | undefined = this._players.get(data.uuid); if (existing) { // update existing diff --git a/webmap/src/sidebar/PlayersTab.ts b/webmap/src/sidebar/PlayersTab.ts index 565be5f83..edb79fc3c 100644 --- a/webmap/src/sidebar/PlayersTab.ts +++ b/webmap/src/sidebar/PlayersTab.ts @@ -1,7 +1,7 @@ import * as L from "leaflet"; import {Pl3xMap} from "../Pl3xMap"; import {Player} from "../player/Player"; -import {createSVGIcon, handleKeyboardEvent, isset} from "../util/Util"; +import {createSVGIcon, handleKeyboardEvent} from "../util/Util"; import BaseTab from "./BaseTab"; import '../svg/players.svg'; import {Lang} from "../settings/Lang"; @@ -67,7 +67,7 @@ export default class PlayersTab extends BaseTab { private _update(): void { const settings: Settings | undefined = this._pl3xmap.settings; - const online: string = String(isset(settings?.players) ? Object.keys(settings!.players).length : '???'); + const online: string = String(this._pl3xmap.playerManager.players.size); const max: string = String(settings?.maxPlayers ?? '???'); const title: any = settings?.lang.players?.label diff --git a/webmap/src/world/World.ts b/webmap/src/world/World.ts index c429ec347..1a19dd3d3 100644 --- a/webmap/src/world/World.ts +++ b/webmap/src/world/World.ts @@ -25,6 +25,8 @@ export class World { private _loaded: boolean = false; + private _eventSource?: EventSource; + private _timer: NodeJS.Timeout | undefined; constructor(pl3xmap: Pl3xMap, worldManager: WorldManager, settings: WorldSettings) { @@ -32,6 +34,27 @@ export class World { this._settings = settings; } + private initSSE() { + this._eventSource = new EventSource("sse/" + this.settings.name); + console.debug("initializing " + this.settings.name + " sse"); + + this._eventSource.addEventListener("markers", (ev: Event) => { + const messageEvent = (ev as MessageEvent); + const json: any = JSON.parse(messageEvent.data); + const key: string = json.key; + const markers: any[] = json.markers; + console.debug(json); + + if (messageEvent.data.length === 0) return; + + this._markerLayers.forEach(layer => { + if (layer.key !== key) return; + layer.timestamp = (new Date()).getTime(); + layer.updateMarkers(markers, this); + }); + }); + } + public load(): Promise { if (this._loaded) { return Promise.resolve(this); @@ -71,6 +94,8 @@ export class World { public unload(): void { clearTimeout(this._timer); // unload and clear markers + this._eventSource?.close(); + console.debug("closing " + this.settings.name + " sse"); this._markerLayers.forEach((layer: MarkerLayer) => layer.unload()) this._markerLayers = []; // unload renderer layer @@ -80,12 +105,13 @@ export class World { } public loadMarkers(): void { + this.initSSE(); getJSON(`tiles/${this.name}/markers.json`) .then((json): void => { (json as MarkerLayer[]).forEach((layer: MarkerLayer): void => { const markerLayer: MarkerLayer = new MarkerLayer(layer.key, layer.label, layer.updateInterval, layer.showControls, layer.defaultHidden, layer.priority, layer.zIndex, layer.pane, layer.css); this._markerLayers.push(markerLayer); - markerLayer.update(this); + markerLayer.update(this, true); }); }); } @@ -209,6 +235,10 @@ export class World { return this._biomePalette; } + get eventSource(): EventSource | undefined { + return this._eventSource; + } + get background(): string { switch (this.type) { case "nether":