diff --git a/build-src/src/main/kotlin/mapmaker.java-binary.gradle.kts b/build-src/src/main/kotlin/mapmaker.java-binary.gradle.kts index 6c31c7e5d..5d9d38bf3 100644 --- a/build-src/src/main/kotlin/mapmaker.java-binary.gradle.kts +++ b/build-src/src/main/kotlin/mapmaker.java-binary.gradle.kts @@ -28,6 +28,12 @@ repositories { includeGroup("com.noxcrew.noxesium") } } + + maven(url = "https://repo.feathermc.net/artifactory/maven-releases") { + content { + includeGroup("net.digitalingot.feather-server-api") + } + } } dependencies { diff --git a/build-src/src/main/kotlin/mapmaker.java-library.gradle.kts b/build-src/src/main/kotlin/mapmaker.java-library.gradle.kts index a13f545d0..f143ba328 100644 --- a/build-src/src/main/kotlin/mapmaker.java-library.gradle.kts +++ b/build-src/src/main/kotlin/mapmaker.java-library.gradle.kts @@ -24,6 +24,12 @@ repositories { includeGroup("com.noxcrew.noxesium") } } + + maven(url = "https://repo.feathermc.net/artifactory/maven-releases") { + content { + includeGroup("net.digitalingot.feather-server-api") + } + } } dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5be28bdfc..393db8195 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,7 @@ blossom = "2.1.0" velocity = "3.4.0-SNAPSHOT" classgraph = "4.8.179" included = "-INCLUDED" +feather = "0.0.5" [libraries] annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" } @@ -50,6 +51,7 @@ jctools = { group = "org.jctools", name = "jctools-core", version.ref = "jctools caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine", version.ref = "caffeine" } json5 = { group = "de.marhali", name = "json5-java", version.ref = "json5" } velocity-api = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" } +feather = { group = "net.digitalingot.feather-server-api", name = "messaging", version.ref = "feather" } included-molang = { group = "dev.hollowcube", name = "molang", version.ref = "included" } diff --git a/modules/common/src/main/java/net/hollowcube/common/util/StringUtil.java b/modules/common/src/main/java/net/hollowcube/common/util/StringUtil.java new file mode 100644 index 000000000..3476fbca6 --- /dev/null +++ b/modules/common/src/main/java/net/hollowcube/common/util/StringUtil.java @@ -0,0 +1,12 @@ +package net.hollowcube.common.util; + +public class StringUtil { + public static String indefiniteArticle(final String word) { + // building -> a building + // adventure -> an adventure + return switch (word.charAt(0)) { + case 'a', 'e', 'i', 'o', 'u' -> "an"; + default -> "a"; + } + " " + word; + } +} diff --git a/modules/compat/build.gradle.kts b/modules/compat/build.gradle.kts index b194a4f50..4372e70a9 100644 --- a/modules/compat/build.gradle.kts +++ b/modules/compat/build.gradle.kts @@ -11,4 +11,5 @@ dependencies { implementation(libs.posthog) implementation(libs.noxesium) implementation(libs.zstd) + implementation(libs.feather) } diff --git a/modules/compat/src/main/java/net/hollowcube/compat/api/discord/DiscordRichPresenceManager.java b/modules/compat/src/main/java/net/hollowcube/compat/api/discord/DiscordRichPresenceManager.java new file mode 100644 index 000000000..b25e186b7 --- /dev/null +++ b/modules/compat/src/main/java/net/hollowcube/compat/api/discord/DiscordRichPresenceManager.java @@ -0,0 +1,36 @@ +package net.hollowcube.compat.api.discord; + +import net.minestom.server.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.ServiceLoader; +import java.util.Set; + + +public class DiscordRichPresenceManager { + private static final Set PROVIDERS = new HashSet<>(); + + static { + ServiceLoader.load(DiscordRichPresenceProvider.class).forEach(PROVIDERS::add); + } + + + public static void setRichPresence(@NotNull Player player, @NotNull String playerState, @NotNull String gameName, @NotNull String gameVariantName) { + for (DiscordRichPresenceProvider provider : PROVIDERS) { + if (provider.isRichPresenceSupportedFor(player)) { + provider.setRichPresence(player, playerState, gameName, gameVariantName); + return; + } + } + } + + public static void clearRichPresence(@NotNull Player player) { + for (DiscordRichPresenceProvider provider : PROVIDERS) { + if (provider.isRichPresenceSupportedFor(player)) { + provider.clearRichPresence(player); + return; + } + } + } +} diff --git a/modules/compat/src/main/java/net/hollowcube/compat/api/discord/DiscordRichPresenceProvider.java b/modules/compat/src/main/java/net/hollowcube/compat/api/discord/DiscordRichPresenceProvider.java new file mode 100644 index 000000000..204f73776 --- /dev/null +++ b/modules/compat/src/main/java/net/hollowcube/compat/api/discord/DiscordRichPresenceProvider.java @@ -0,0 +1,12 @@ +package net.hollowcube.compat.api.discord; + +import net.minestom.server.entity.Player; +import org.jetbrains.annotations.NotNull; + +public interface DiscordRichPresenceProvider { + void setRichPresence(@NotNull Player player, @NotNull String playerState, @NotNull String gameName, @NotNull String gameVariantName); + + void clearRichPresence(@NotNull Player player); + + boolean isRichPresenceSupportedFor(@NotNull Player player); +} diff --git a/modules/compat/src/main/java/net/hollowcube/compat/feather/FeatherCompatProvider.java b/modules/compat/src/main/java/net/hollowcube/compat/feather/FeatherCompatProvider.java new file mode 100644 index 000000000..6028bbc06 --- /dev/null +++ b/modules/compat/src/main/java/net/hollowcube/compat/feather/FeatherCompatProvider.java @@ -0,0 +1,152 @@ +package net.hollowcube.compat.feather; + +import com.google.auto.service.AutoService; +import net.digitalingot.feather.serverapi.messaging.*; +import net.digitalingot.feather.serverapi.messaging.messages.client.S2CClearDiscordActivity; +import net.digitalingot.feather.serverapi.messaging.messages.client.S2CHandshake; +import net.digitalingot.feather.serverapi.messaging.messages.client.S2CSetDiscordActivity; +import net.digitalingot.feather.serverapi.messaging.messages.server.C2SClientHello; +import net.digitalingot.feather.serverapi.messaging.messages.server.C2SHandshake; +import net.hollowcube.compat.api.CompatProvider; +import net.hollowcube.compat.api.discord.DiscordRichPresenceProvider; +import net.hollowcube.compat.api.packet.PacketRegistry; +import net.hollowcube.compat.feather.packets.ClientboundFeatherPacket; +import net.hollowcube.compat.feather.packets.ServerboundFeatherPacket; +import net.minestom.server.entity.Player; +import net.minestom.server.event.GlobalEventHandler; +import net.minestom.server.event.player.PlayerDisconnectEvent; +import net.minestom.server.tag.Tag; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@AutoService({CompatProvider.class, DiscordRichPresenceProvider.class}) +public class FeatherCompatProvider implements CompatProvider, DiscordRichPresenceProvider { + private static final String IMAGE_URL = "https://servermappings.lunarclientcdn.com/logos/hollowcube.png"; + private static final Tag FEATHER_SUPPORT_ENABLED = Tag.Transient("mapmaker:feather/enabled"); + private static final Handshaking HANDSHAKING = new Handshaking(); + + @Override + public void registerListeners(GlobalEventHandler events) { + events.addListener(PlayerDisconnectEvent.class, e -> HANDSHAKING.finish(e.getPlayer())); + + } + + @Override + public void registerPackets(PacketRegistry registry) { + registry.register(ClientboundFeatherPacket.TYPE); + registry.register(ServerboundFeatherPacket.TYPE, (player, packet) -> { + if (!player.hasTag(FEATHER_SUPPORT_ENABLED)) { + final var hello = HANDSHAKING.handle(player, packet.message()); + + if (hello != null) { + player.setTag(FEATHER_SUPPORT_ENABLED, true); + } + + } + }); + } + + @Override + public void setRichPresence(@NotNull Player player, @NotNull String playerState, @NotNull String gameName, @NotNull String gameVariantName) { + new ClientboundFeatherPacket( + new S2CSetDiscordActivity( + IMAGE_URL, + "Hollow Cube", + gameVariantName, + playerState + " " + gameName + " on Hollow Cube", + null, + null, + null, + null + ) + ).send(player); + } + + @Override + public void clearRichPresence(@NotNull Player player) { + new ClientboundFeatherPacket(new S2CClearDiscordActivity()).send(player); + } + + @Override + public boolean isRichPresenceSupportedFor(@NotNull Player player) { + return player.hasTag(FEATHER_SUPPORT_ENABLED); + } + + // This is directly copied from https://github.com/FeatherMC/feather-server-api/blob/main/bukkit/src/main/java/net/digitalingot/feather/serverapi/bukkit/messaging/BukkitMessagingService.java + // but with some modifications to make it work with Minestom + private static class Handshaking { + private final Map handshakes = new HashMap<>(); + + + private HandshakeState getState(Player player) { + return this.handshakes.getOrDefault(player.getUuid(), HandshakeState.EXPECTING_HANDSHAKE); + } + + private void setState(UUID playerId, HandshakeState state) { + this.handshakes.put(playerId, state); + } + + private void accept(Player player) { + setState(player.getUuid(), HandshakeState.EXPECTING_HELLO); + new ClientboundFeatherPacket(new S2CHandshake()).send(player); + } + + private void reject(Player player) { + setState(player.getUuid(), HandshakeState.REJECTED); + } + + private void finish(Player player) { + this.handshakes.remove(player.getUuid()); + } + + private C2SClientHello handle(Player player, Message message) { + HandshakeState state = getState(player); + + if (state == HandshakeState.REJECTED) { + return null; + } + + + if (state == HandshakeState.EXPECTING_HANDSHAKE) { + if (handleExpectingHandshake(message)) { + accept(player); + } else { + reject(player); + } + } else if (state == HandshakeState.EXPECTING_HELLO) { + if ((message instanceof C2SClientHello)) { + finish(player); + return (C2SClientHello) message; + } + reject(player); + } + + return null; + } + + private boolean handleExpectingHandshake(Message message) { + if (!(message instanceof C2SHandshake handshake)) { + return false; + } + int protocolVersion = handshake.getProtocolVersion(); + if (protocolVersion > MessageConstants.VERSION) { + // In the official API Implementation, a mismatched API version just alerts players with a permission that it is out of date. + // It still processes packets fine. + // There is no indication of what versioning compatibility we can expect since they've only released one version. + // For now, we can probably just ignore this. + } + return true; + } + + + private enum HandshakeState { + EXPECTING_HANDSHAKE, + EXPECTING_HELLO, + REJECTED + } + } + +} diff --git a/modules/compat/src/main/java/net/hollowcube/compat/feather/packets/ClientboundFeatherPacket.java b/modules/compat/src/main/java/net/hollowcube/compat/feather/packets/ClientboundFeatherPacket.java new file mode 100644 index 000000000..71430dd6c --- /dev/null +++ b/modules/compat/src/main/java/net/hollowcube/compat/feather/packets/ClientboundFeatherPacket.java @@ -0,0 +1,35 @@ +package net.hollowcube.compat.feather.packets; + +import net.digitalingot.feather.serverapi.messaging.ClientMessageHandler; +import net.digitalingot.feather.serverapi.messaging.Message; +import net.digitalingot.feather.serverapi.messaging.MessageDecoder; +import net.digitalingot.feather.serverapi.messaging.MessageEncoder; +import net.hollowcube.compat.api.packet.ClientboundModPacket; +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +// Feather supports a fragmented packet channel for messages that exceed the packet size limit, which was set lower on older versions of Bukkit +// We don't ever send messages that get that big, so for now this can be ignored. +// If we ever utilise more of their UI features, this may be needed in the future. +public record ClientboundFeatherPacket( + @NotNull Message message +) implements ClientboundModPacket { + public static final Type TYPE = Type.of( + "feather", + "client", + NetworkBuffer.RAW_BYTES.transform(ClientboundFeatherPacket::new, ClientboundFeatherPacket::toBytes) + ); + + private ClientboundFeatherPacket(byte[] bytes) { + this(MessageDecoder.CLIENT_BOUND.decode(bytes)); + } + + public byte[] toBytes() { + return MessageEncoder.CLIENT_BOUND.encode(message); + } + + @Override + public Type getType() { + return TYPE; + } +} diff --git a/modules/compat/src/main/java/net/hollowcube/compat/feather/packets/ServerboundFeatherPacket.java b/modules/compat/src/main/java/net/hollowcube/compat/feather/packets/ServerboundFeatherPacket.java new file mode 100644 index 000000000..8c66a83d7 --- /dev/null +++ b/modules/compat/src/main/java/net/hollowcube/compat/feather/packets/ServerboundFeatherPacket.java @@ -0,0 +1,32 @@ +package net.hollowcube.compat.feather.packets; + +import net.digitalingot.feather.serverapi.messaging.Message; +import net.digitalingot.feather.serverapi.messaging.MessageDecoder; +import net.digitalingot.feather.serverapi.messaging.MessageEncoder; +import net.digitalingot.feather.serverapi.messaging.ServerMessageHandler; +import net.hollowcube.compat.api.packet.ServerboundModPacket; +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +public record ServerboundFeatherPacket( + @NotNull Message message +) implements ServerboundModPacket { + public static final Type TYPE = Type.of( + "feather", + "client", + NetworkBuffer.RAW_BYTES.transform(ServerboundFeatherPacket::new, ServerboundFeatherPacket::toBytes) + ); + + private ServerboundFeatherPacket(byte[] bytes) { + this(MessageDecoder.SERVER_BOUND.decode(bytes)); + } + + public byte[] toBytes() { + return MessageEncoder.SERVER_BOUND.encode(message); + } + + @Override + public Type getType() { + return TYPE; + } +} diff --git a/modules/compat/src/main/java/net/hollowcube/compat/lunar/LunarCompatProvider.java b/modules/compat/src/main/java/net/hollowcube/compat/lunar/LunarCompatProvider.java new file mode 100644 index 000000000..d5c18f7ab --- /dev/null +++ b/modules/compat/src/main/java/net/hollowcube/compat/lunar/LunarCompatProvider.java @@ -0,0 +1,98 @@ +package net.hollowcube.compat.lunar; + +import com.google.auto.service.AutoService; +import net.hollowcube.compat.api.CompatProvider; +import net.hollowcube.compat.api.discord.DiscordRichPresenceProvider; +import net.hollowcube.compat.api.packet.PacketRegistry; +import net.hollowcube.compat.lunar.packets.ClientboundLunarPacket; +import net.hollowcube.compat.lunar.packets.ServerboundLunarPacket; +import net.minestom.server.entity.Player; +import net.minestom.server.event.GlobalEventHandler; +import net.minestom.server.event.player.PlayerPluginMessageEvent; +import net.minestom.server.tag.Tag; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + + +@AutoService({CompatProvider.class, DiscordRichPresenceProvider.class}) +public class LunarCompatProvider implements CompatProvider, DiscordRichPresenceProvider { + public static final Tag LUNAR_SUPPORT_ENABLED = Tag.Transient("mapmaker:lunar/enabled"); + private static final ClientboundLunarPacket MOD_SETTINGS = new ClientboundLunarPacket( + Map.of( + "@type", ClientboundLunarPacket.TYPE_PREFIX + "configurable.v1.ConfigurableSettings", + "apollo_module", "mod_setting", + "enable", true, + // todo there is probably a cleaner and more configurable way to do this + "properties", Map.of( + "bossbar.enabled", false, + "saturation.enabled", false, + "direction-hud.enabled", false, + "armorstatus.enabled", false, + "title.enabled", false + ) + )); + private static final ClientboundLunarPacket ENABLE_RICH_PRESENCE = new ClientboundLunarPacket( + Map.of( + "@type", ClientboundLunarPacket.TYPE_PREFIX + "configurable.v1.ConfigurableSettings", + "apollo_module", "rich_presence", + "enable", true + ) + ); + + @Override + public void registerListeners(GlobalEventHandler events) { + events.addListener(PlayerPluginMessageEvent.class, this::handleLunarPacket); + } + + @Override + public void registerPackets(PacketRegistry registry) { + registry.register(ClientboundLunarPacket.TYPE); + registry.register(ServerboundLunarPacket.LUNAR_APOLLO_TYPE, (player, packet) -> { + }); + registry.register(ServerboundLunarPacket.APOLLO_JSON_TYPE, (player, packet) -> { + }); + + } + + private void handleLunarPacket(PlayerPluginMessageEvent event) { + if (event.getPlayer().hasTag(LUNAR_SUPPORT_ENABLED)) { + return; + } + + if (event.getIdentifier().equalsIgnoreCase("minecraft:register")) { + if (event.getMessageString().contains("lunar:apollo") || event.getMessageString().contains("apollo:json")) { + event.getPlayer().setTag(LUNAR_SUPPORT_ENABLED, true); + MOD_SETTINGS.send(event.getPlayer()); + ENABLE_RICH_PRESENCE.send(event.getPlayer()); + } + } + } + + @Override + public void setRichPresence(@NotNull Player player, @NotNull String playerState, @NotNull String gameName, @NotNull String gameVariantName) { + new ClientboundLunarPacket( + Map.of( + "@type", ClientboundLunarPacket.TYPE_PREFIX + "richpresence.v1.OverrideServerRichPresenceMessage", + "game_name", gameName, + // This is a lunar bug, it seems to escape the / character and discord doesn't undo it, so we need to replace it with another character + "game_variant_name", gameVariantName.replace("/", "∕"), + "player_state", playerState + ) + ).send(player); + } + + @Override + public void clearRichPresence(@NotNull Player player) { + new ClientboundLunarPacket( + Map.of( + "@type", ClientboundLunarPacket.TYPE_PREFIX + "richpresence.v1.ResetServerRichPresenceMessage" + ) + ).send(player); + } + + @Override + public boolean isRichPresenceSupportedFor(@NotNull Player player) { + return player.hasTag(LUNAR_SUPPORT_ENABLED); + } +} diff --git a/modules/compat/src/main/java/net/hollowcube/compat/lunar/packets/ClientboundLunarPacket.java b/modules/compat/src/main/java/net/hollowcube/compat/lunar/packets/ClientboundLunarPacket.java new file mode 100644 index 000000000..820096443 --- /dev/null +++ b/modules/compat/src/main/java/net/hollowcube/compat/lunar/packets/ClientboundLunarPacket.java @@ -0,0 +1,28 @@ +package net.hollowcube.compat.lunar.packets; + +import com.google.gson.Gson; +import net.hollowcube.compat.api.packet.ClientboundModPacket; +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public record ClientboundLunarPacket( + @NotNull Map data +) implements ClientboundModPacket { + private static final Gson GSON = new Gson(); + public static final String TYPE_PREFIX = "type.googleapis.com/lunarclient.apollo."; + + + public static final Type TYPE = Type.of( + "apollo", + "json", + (buffer, packet) -> buffer.write(NetworkBuffer.RAW_BYTES, GSON.toJson(packet.data).getBytes(StandardCharsets.UTF_8)) + ); + + @Override + public Type getType() { + return TYPE; + } +} diff --git a/modules/compat/src/main/java/net/hollowcube/compat/lunar/packets/ServerboundLunarPacket.java b/modules/compat/src/main/java/net/hollowcube/compat/lunar/packets/ServerboundLunarPacket.java new file mode 100644 index 000000000..d1291c853 --- /dev/null +++ b/modules/compat/src/main/java/net/hollowcube/compat/lunar/packets/ServerboundLunarPacket.java @@ -0,0 +1,25 @@ +package net.hollowcube.compat.lunar.packets; + +import net.hollowcube.compat.api.packet.ServerboundModPacket; + +// For lunar to advertise itself we have to ensure we register the channels +// Rather than send another register message, we'll just register some dummy handlers +// Who knows, maybe they'll be useful in the future if lunar add more serverbound packets +public record ServerboundLunarPacket() implements ServerboundModPacket { + public static final Type LUNAR_APOLLO_TYPE = Type.of( + "lunar", + "apollo", + buffer -> new ServerboundLunarPacket() + ); + + public static final Type APOLLO_JSON_TYPE = Type.of( + "apollo", + "json", + buffer -> new ServerboundLunarPacket() + ); + + @Override + public Type getType() { + return null; + } +} diff --git a/modules/hub/build.gradle.kts b/modules/hub/build.gradle.kts index 15118a1f7..ec832704b 100644 --- a/modules/hub/build.gradle.kts +++ b/modules/hub/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { implementation(project(":modules:canvas:api")) implementation(project(":modules:terraform")) // Included for schematics, todo both terraform and this module should get schem from central // implementation(project(":modules:script-engine")) + implementation(project(":modules:compat")) implementation(libs.minestom) implementation(libs.gson) diff --git a/modules/hub/src/main/java/net/hollowcube/mapmaker/hub/feature/misc/DiscordRichPresenceFeature.java b/modules/hub/src/main/java/net/hollowcube/mapmaker/hub/feature/misc/DiscordRichPresenceFeature.java new file mode 100644 index 000000000..5b5225b25 --- /dev/null +++ b/modules/hub/src/main/java/net/hollowcube/mapmaker/hub/feature/misc/DiscordRichPresenceFeature.java @@ -0,0 +1,17 @@ +package net.hollowcube.mapmaker.hub.feature.misc; + +import com.google.auto.service.AutoService; +import net.hollowcube.compat.api.discord.DiscordRichPresenceManager; +import net.hollowcube.mapmaker.hub.HubMapWorld; +import net.hollowcube.mapmaker.hub.feature.HubFeature; +import net.hollowcube.mapmaker.map.MapServer; +import net.minestom.server.event.player.PlayerLoadedEvent; +import org.jetbrains.annotations.NotNull; + +@AutoService(HubFeature.class) +public class DiscordRichPresenceFeature implements HubFeature { + @Override + public void load(@NotNull MapServer server, @NotNull HubMapWorld world) { + world.eventNode().addListener(PlayerLoadedEvent.class, event -> DiscordRichPresenceManager.setRichPresence(event.getPlayer(), "In", "the lobby", "")); + } +} diff --git a/modules/map/src/main/java/net/hollowcube/mapmaker/map/feature/common/DiscordRichPresenceFeatureProvider.java b/modules/map/src/main/java/net/hollowcube/mapmaker/map/feature/common/DiscordRichPresenceFeatureProvider.java new file mode 100644 index 000000000..1e35e11a6 --- /dev/null +++ b/modules/map/src/main/java/net/hollowcube/mapmaker/map/feature/common/DiscordRichPresenceFeatureProvider.java @@ -0,0 +1,41 @@ +package net.hollowcube.mapmaker.map.feature.common; + +import com.google.auto.service.AutoService; +import net.hollowcube.common.util.StringUtil; +import net.hollowcube.compat.api.discord.DiscordRichPresenceManager; +import net.hollowcube.mapmaker.map.MapWorld; +import net.hollowcube.mapmaker.map.event.MapPlayerInitEvent; +import net.hollowcube.mapmaker.map.feature.FeatureProvider; +import net.hollowcube.mapmaker.map.world.EditingMapWorld; +import net.hollowcube.mapmaker.map.world.PlayingMapWorld; +import net.minestom.server.event.EventFilter; +import net.minestom.server.event.EventNode; +import net.minestom.server.event.trait.InstanceEvent; +import org.jetbrains.annotations.NotNull; + +@AutoService(FeatureProvider.class) +public class DiscordRichPresenceFeatureProvider implements FeatureProvider { + private final EventNode eventNode = EventNode.type("mapmaker:map/discord-rich-presence", EventFilter.INSTANCE) + .addListener(MapPlayerInitEvent.class, this::handleMapInit); + + @Override + public boolean initMap(@NotNull MapWorld world) { + if (!(world instanceof PlayingMapWorld || world instanceof EditingMapWorld)) + return false; + world.eventNode().addChild(eventNode); + return true; + } + + private void handleMapInit(@NotNull final MapPlayerInitEvent event) { + if (event.isMapJoin()) { + if (event.mapWorld() instanceof PlayingMapWorld) { + DiscordRichPresenceManager.setRichPresence(event.player(), "Playing", event.mapWorld().map().name(), "/play " + event.mapWorld().map().publishedIdString()); + } else if (event.mapWorld() instanceof EditingMapWorld) { + final var variant = event.mapWorld().map().settings().getVariant().name().toLowerCase(); + DiscordRichPresenceManager.setRichPresence(event.getPlayer(), "Building", "a map", "Building " + StringUtil.indefiniteArticle(variant) + " map"); + } + } + } + +} + diff --git a/modules/map/src/main/java/net/hollowcube/mapmaker/map/world/EditingMapWorld.java b/modules/map/src/main/java/net/hollowcube/mapmaker/map/world/EditingMapWorld.java index 436c4c480..bffb4b99e 100644 --- a/modules/map/src/main/java/net/hollowcube/mapmaker/map/world/EditingMapWorld.java +++ b/modules/map/src/main/java/net/hollowcube/mapmaker/map/world/EditingMapWorld.java @@ -7,6 +7,7 @@ import net.hollowcube.mapmaker.ExceptionReporter; import net.hollowcube.mapmaker.instance.generation.MapGenerators; import net.hollowcube.mapmaker.map.*; +import net.hollowcube.mapmaker.map.event.MapPlayerInitEvent; import net.hollowcube.mapmaker.map.feature.edit.TeleportHistoryFeatureProvider; import net.hollowcube.mapmaker.map.instance.MapInstance; import net.hollowcube.mapmaker.map.item.ItemTags; @@ -316,6 +317,8 @@ public void addPlayer(@NotNull Player player) { // If there is no position stored then this is a fresh edit state so add the builder menu player.getInventory().addItemStack(itemRegistry().getItemStack("mapmaker:builder_menu", null)); } + + callEvent(new MapPlayerInitEvent(this, player, true, true)); } private @NotNull SaveState getOrCreateSaveState(@NotNull Player player) {