From 560319fdfdfd4a94becabe05a76026e670284a8c Mon Sep 17 00:00:00 2001 From: Piotr Zych <77621271+P1otrulla@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:16:28 +0200 Subject: [PATCH 01/13] * Introduced `AdvancementResolver` for managing advancements via PacketEvents. * Added new module: `multification-bukkit-packetevents`. * Updated dependencies and repository configurations. --- buildSrc/src/main/kotlin/Versions.kt | 2 + .../multification-repositories.gradle.kts | 9 +- .../build.gradle.kts | 16 ++ .../packetevents/AdvancementResolver.java | 217 ++++++++++++++++++ .../advancement/AdvancementContent.java | 29 +++ .../advancement/AdvancementFrameType.java | 7 + .../advancement/PacketEventsNoticeKey.java | 9 + settings.gradle.kts | 3 + 8 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 multification-bukkit-packetevents/build.gradle.kts create mode 100644 multification-bukkit-packetevents/src/main/java/com/eternalcode/multification/packetevents/AdvancementResolver.java create mode 100644 multification-core/src/com/eternalcode/multification/notice/resolver/advancement/AdvancementContent.java create mode 100644 multification-core/src/com/eternalcode/multification/notice/resolver/advancement/AdvancementFrameType.java create mode 100644 multification-core/src/com/eternalcode/multification/notice/resolver/advancement/PacketEventsNoticeKey.java diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index dabc94e..19a7b60 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -12,6 +12,8 @@ object Versions { const val SPIGOT_API = "1.21.4-R0.1-SNAPSHOT" + const val PACKETEVENTS = "2.9.5" + const val JETBRAINS_ANNOTATIONS = "26.0.2-1" } diff --git a/buildSrc/src/main/kotlin/multification-repositories.gradle.kts b/buildSrc/src/main/kotlin/multification-repositories.gradle.kts index 3ee91b7..118ed87 100644 --- a/buildSrc/src/main/kotlin/multification-repositories.gradle.kts +++ b/buildSrc/src/main/kotlin/multification-repositories.gradle.kts @@ -5,10 +5,11 @@ plugins { repositories { mavenCentral() - maven("https://papermc.io/repo/repository/maven-public/") // paper, adventure, velocity maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // spigot - maven("https://repo.panda-lang.org/releases/") // expressible - maven("https://repo.stellardrift.ca/repository/snapshots/") - maven("https://storehouse.okaeri.eu/repository/maven-public/") // okaeri configs maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // adventure snapshots + maven("https://storehouse.okaeri.eu/repository/maven-public/") // okaeri configs + maven("https://repo.stellardrift.ca/repository/snapshots/") // ? xd + maven("https://repo.codemc.io/repository/maven-releases/") // packetevents + maven("https://papermc.io/repo/repository/maven-public/") // paper, adventure, velocity + maven("https://repo.panda-lang.org/releases/") // expressible } \ No newline at end of file diff --git a/multification-bukkit-packetevents/build.gradle.kts b/multification-bukkit-packetevents/build.gradle.kts new file mode 100644 index 0000000..b8cfddb --- /dev/null +++ b/multification-bukkit-packetevents/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + `multification-java` + `multification-java-17` + `multification-repositories` + `multification-publish` +} + +dependencies { + api(project(":multification-core")) + + compileOnly("org.spigotmc:spigot-api:${Versions.SPIGOT_API}") + testImplementation("org.spigotmc:spigot-api:${Versions.SPIGOT_API}") + + compileOnly("com.github.retrooper:packetevents-spigot:${Versions.PACKETEVENTS}") + testImplementation("com.github.retrooper:packetevents-spigot:${Versions.PACKETEVENTS}") +} \ No newline at end of file diff --git a/multification-bukkit-packetevents/src/main/java/com/eternalcode/multification/packetevents/AdvancementResolver.java b/multification-bukkit-packetevents/src/main/java/com/eternalcode/multification/packetevents/AdvancementResolver.java new file mode 100644 index 0000000..6f1c229 --- /dev/null +++ b/multification-bukkit-packetevents/src/main/java/com/eternalcode/multification/packetevents/AdvancementResolver.java @@ -0,0 +1,217 @@ +package main.java.com.eternalcode.multification.packetevents; + +import com.eternalcode.multification.notice.NoticeKey; +import com.eternalcode.multification.notice.resolver.NoticeSerdesResult; +import com.eternalcode.multification.notice.resolver.advancement.AdvancementContent; +import com.eternalcode.multification.notice.resolver.advancement.AdvancementFrameType; +import com.eternalcode.multification.notice.resolver.advancement.PacketEventsNoticeKey; +import com.eternalcode.multification.notice.resolver.text.TextContentResolver; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.protocol.item.ItemStack; +import com.github.retrooper.packetevents.protocol.item.type.ItemType; +import com.github.retrooper.packetevents.protocol.item.type.ItemTypes; +import com.github.retrooper.packetevents.protocol.player.User; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.UnaryOperator; + +public class AdvancementResolver implements TextContentResolver { + + private static final String FORMAT = "%s|%s|%s|%s"; + private static final long CLEANUP_DELAY_TICKS = 20L; + + private final NoticeKey key; + private final Plugin plugin; + + public AdvancementResolver(Plugin plugin) { + this.key = PacketEventsNoticeKey.ADVANCEMENT; + this.plugin = plugin; + } + + @Override + public NoticeKey noticeKey() { + return this.key; + } + + @Override + public void send(Audience audience, ComponentSerializer serializer, AdvancementContent content) { + if (!(audience instanceof Player player)) { + return; + } + + User user = PacketEvents.getAPI().getPlayerManager().getUser(player); + if (user == null) { + return; + } + + // Generate unique advancement key for this toast + String advancementKey = "toast_" + UUID.randomUUID().toString().replace("-", ""); + + Component titleComponent = serializer.deserialize(content.title()); + Component descComponent = serializer.deserialize(content.description()); + + ItemStack icon = this.createIcon(content.iconOrDefault()); + int flags = 0x01; // SHOW_TOAST flag + + WrapperPlayServerAdvancements.AdvancementDisplay display = new WrapperPlayServerAdvancements.AdvancementDisplay( + titleComponent, + descComponent, + icon, + this.toFrameTypeOrdinal(content.frameTypeOrDefault()), + flags, + Optional.empty(), + 0.0f, + 0.0f + ); + + WrapperPlayServerAdvancements.Advancement advancement = new WrapperPlayServerAdvancements.Advancement( + Optional.empty(), + Optional.of(display), + new ArrayList<>(), + Optional.empty() + ); + + Map advancements = new HashMap<>(); + advancements.put(advancementKey, advancement); + + // Packet 1: Add advancement to client + WrapperPlayServerAdvancements addPacket = new WrapperPlayServerAdvancements( + false, + advancements, + new ArrayList<>(), + new HashMap<>() + ); + user.sendPacket(addPacket); + + // Packet 2: Grant criteria to trigger toast display + Map progressMap = new HashMap<>(); + Map criteria = new HashMap<>(); + criteria.put("trigger", System.currentTimeMillis()); + + progressMap.put(advancementKey, new WrapperPlayServerAdvancements.AdvancementProgress(criteria)); + + WrapperPlayServerAdvancements grantPacket = new WrapperPlayServerAdvancements( + false, + new HashMap<>(), + new ArrayList<>(), + progressMap + ); + user.sendPacket(grantPacket); + + Bukkit.getScheduler().runTaskLater( + this.plugin, + () -> { + WrapperPlayServerAdvancements removePacket = new WrapperPlayServerAdvancements( + false, + new HashMap<>(), + Collections.singletonList(advancementKey), + new HashMap<>() + ); + user.sendPacket(removePacket); + }, + CLEANUP_DELAY_TICKS + ); + } + + @Override + public NoticeSerdesResult serialize(AdvancementContent content) { + return new NoticeSerdesResult.Single(String.format(FORMAT, + content.title(), + content.description(), + content.iconOrDefault(), + content.frameTypeOrDefault().name() + )); + } + + @Override + public Optional deserialize(NoticeSerdesResult result) { + return result.firstElement().map(value -> { + String[] parts = value.split("\\|"); + + if (parts.length < 2) { + throw new IllegalArgumentException("Invalid advancement format: " + value); + } + + String title = parts[0]; + String description = parts[1]; + String icon = parts.length > 2 ? parts[2] : null; + AdvancementFrameType frameType = parts.length > 3 + ? AdvancementFrameType.valueOf(parts[3]) + : null; + + return new AdvancementContent(title, description, icon, frameType); + }); + } + + @Override + public AdvancementContent createFromText(List contents) { + if (contents.isEmpty()) { + return new AdvancementContent("", "", null, null); + } + + if (contents.size() == 1) { + return new AdvancementContent(contents.get(0), "", null, null); + } + + return new AdvancementContent(contents.get(0), contents.get(1), null, null); + } + + @Override + public AdvancementContent applyText(AdvancementContent content, UnaryOperator function) { + return new AdvancementContent( + function.apply(content.title()), + function.apply(content.description()), + content.icon(), + content.frameType() + ); + } + + /** + * Creates an ItemStack icon from material name string. + * Falls back to GRASS_BLOCK if material is invalid. + * + * @param materialName the material name (e.g., "OAK_SAPLING", "DIAMOND") + * @return ItemStack with the specified material type + */ + private ItemStack createIcon(@NotNull String materialName) { + try { + ItemType materialType = ItemTypes.getByName(materialName); + + if (materialType == null) { + throw new IllegalArgumentException("Invalid material: " + materialName); + } + + return ItemStack.builder() + .type(materialType) + .build(); + } + catch (IllegalArgumentException e) { + return ItemStack.builder() + .type(ItemTypes.GRASS_BLOCK) + .amount(1) + .build(); + } + } + + /** + * Converts AdvancementFrameType enum to protocol ordinal. + * PacketEvents uses int ordinal instead of enum for FrameType. + * + * @param frameType the frame type enum + * @return protocol ordinal (0 = TASK, 1 = CHALLENGE, 2 = GOAL) + */ + private int toFrameTypeOrdinal(AdvancementFrameType frameType) { + return switch (frameType) { + case TASK -> 0; // Yellow frame (normal achievement) + case CHALLENGE -> 1; // Purple frame (challenge) + case GOAL -> 2; // Rounded frame (goal) + }; + } +} \ No newline at end of file diff --git a/multification-core/src/com/eternalcode/multification/notice/resolver/advancement/AdvancementContent.java b/multification-core/src/com/eternalcode/multification/notice/resolver/advancement/AdvancementContent.java new file mode 100644 index 0000000..3847b5d --- /dev/null +++ b/multification-core/src/com/eternalcode/multification/notice/resolver/advancement/AdvancementContent.java @@ -0,0 +1,29 @@ +package com.eternalcode.multification.notice.resolver.advancement; + +import com.eternalcode.multification.notice.resolver.text.TextContent; +import java.util.List; +import org.jetbrains.annotations.Nullable; + +public record AdvancementContent( + String title, + String description, + @Nullable String icon, + @Nullable AdvancementFrameType frameType +) implements TextContent { + + public static final String DEFAULT_ICON = "GRASS_BLOCK"; + public static final AdvancementFrameType DEFAULT_FRAME = AdvancementFrameType.TASK; + + @Override + public List contents() { + return List.of(this.title, this.description); + } + + public String iconOrDefault() { + return this.icon != null ? this.icon : DEFAULT_ICON; + } + + public AdvancementFrameType frameTypeOrDefault() { + return this.frameType != null ? this.frameType : DEFAULT_FRAME; + } +} \ No newline at end of file diff --git a/multification-core/src/com/eternalcode/multification/notice/resolver/advancement/AdvancementFrameType.java b/multification-core/src/com/eternalcode/multification/notice/resolver/advancement/AdvancementFrameType.java new file mode 100644 index 0000000..83c110b --- /dev/null +++ b/multification-core/src/com/eternalcode/multification/notice/resolver/advancement/AdvancementFrameType.java @@ -0,0 +1,7 @@ +package com.eternalcode.multification.notice.resolver.advancement; + +public enum AdvancementFrameType { + TASK, + CHALLENGE, + GOAL +} diff --git a/multification-core/src/com/eternalcode/multification/notice/resolver/advancement/PacketEventsNoticeKey.java b/multification-core/src/com/eternalcode/multification/notice/resolver/advancement/PacketEventsNoticeKey.java new file mode 100644 index 0000000..f0d85f5 --- /dev/null +++ b/multification-core/src/com/eternalcode/multification/notice/resolver/advancement/PacketEventsNoticeKey.java @@ -0,0 +1,9 @@ +package com.eternalcode.multification.notice.resolver.advancement; + +import com.eternalcode.multification.notice.NoticeKey; +import com.eternalcode.multification.notice.resolver.NoticeContent; + +public interface PacketEventsNoticeKey extends NoticeKey { + + NoticeKey ADVANCEMENT = NoticeKey.of("advancement", AdvancementContent.class); +} diff --git a/settings.gradle.kts b/settings.gradle.kts index f85e277..74e9805 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,3 +6,6 @@ include("multification-okaeri") include("multification-bukkit") include("examples:bukkit") + +include("multification-packetevents") +include("multification-bukkit-packetevents") \ No newline at end of file From 10a004182ef8d51bc7875eb37b4387af8f64932f Mon Sep 17 00:00:00 2001 From: Piotr Zych <77621271+P1otrulla@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:18:41 +0200 Subject: [PATCH 02/13] Reordered and restored `examples:bukkit` module in `settings.gradle.kts`. --- settings.gradle.kts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 74e9805..764de79 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,8 +4,6 @@ include("multification-core") include("multification-cdn") include("multification-okaeri") include("multification-bukkit") +include("multification-packetevents") include("examples:bukkit") - -include("multification-packetevents") -include("multification-bukkit-packetevents") \ No newline at end of file From a03d42e22940f164881b7b7afcd4474440dd6ff8 Mon Sep 17 00:00:00 2001 From: Piotr Zych <77621271+P1otrulla@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:40:42 +0200 Subject: [PATCH 03/13] draft --- .../okaeri}/AdvancementResolver.java | 74 +++++++++---------- .../build.gradle.kts | 0 .../packetevents/PacketEventsTest.java | 8 ++ settings.gradle.kts | 2 +- 4 files changed, 46 insertions(+), 38 deletions(-) rename {multification-bukkit-packetevents/src/main/java/com/eternalcode/multification/packetevents => multification-okaeri/src/com/eternalcode/multification/okaeri}/AdvancementResolver.java (71%) rename {multification-bukkit-packetevents => multification-packetevents}/build.gradle.kts (100%) create mode 100644 multification-packetevents/test/com/eternalcode/multification/packetevents/PacketEventsTest.java diff --git a/multification-bukkit-packetevents/src/main/java/com/eternalcode/multification/packetevents/AdvancementResolver.java b/multification-okaeri/src/com/eternalcode/multification/okaeri/AdvancementResolver.java similarity index 71% rename from multification-bukkit-packetevents/src/main/java/com/eternalcode/multification/packetevents/AdvancementResolver.java rename to multification-okaeri/src/com/eternalcode/multification/okaeri/AdvancementResolver.java index 6f1c229..9702d6b 100644 --- a/multification-bukkit-packetevents/src/main/java/com/eternalcode/multification/packetevents/AdvancementResolver.java +++ b/multification-okaeri/src/com/eternalcode/multification/okaeri/AdvancementResolver.java @@ -1,16 +1,16 @@ -package main.java.com.eternalcode.multification.packetevents; +package com.eternalcode.multification.okaeri; import com.eternalcode.multification.notice.NoticeKey; import com.eternalcode.multification.notice.resolver.NoticeSerdesResult; -import com.eternalcode.multification.notice.resolver.advancement.AdvancementContent; -import com.eternalcode.multification.notice.resolver.advancement.AdvancementFrameType; -import com.eternalcode.multification.notice.resolver.advancement.PacketEventsNoticeKey; import com.eternalcode.multification.notice.resolver.text.TextContentResolver; +import com.eternalcode.multification.packetevents.notice.PacketEventsNoticeKey; import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.protocol.advancements.*; import com.github.retrooper.packetevents.protocol.item.ItemStack; import com.github.retrooper.packetevents.protocol.item.type.ItemType; import com.github.retrooper.packetevents.protocol.item.type.ItemTypes; import com.github.retrooper.packetevents.protocol.player.User; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerAdvancementsUpdate; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.ComponentSerializer; @@ -52,67 +52,68 @@ public void send(Audience audience, ComponentSerializer