Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import dev.pgm.community.polls.feature.PollFeature;
import dev.pgm.community.requests.feature.RequestFeature;
import dev.pgm.community.requests.feature.types.SQLRequestFeature;
import dev.pgm.community.serverlinks.ServerLinksFeature;
import dev.pgm.community.sessions.feature.SessionFeature;
import dev.pgm.community.sessions.feature.types.SQLSessionFeature;
import dev.pgm.community.squads.SquadFeature;
Expand Down Expand Up @@ -60,6 +61,7 @@ public class FeatureManager {
private final PollFeature polls;
private final SquadFeature squads;
private final MatchHistoryFeature history;
private final ServerLinksFeature serverLinks;

public FeatureManager(
Configuration config,
Expand Down Expand Up @@ -97,6 +99,7 @@ public FeatureManager(
this.polls = new PollFeature(config, logger);
this.squads = new SquadFeature(config, logger);
this.history = new MatchHistoryFeature(config, logger);
this.serverLinks = new ServerLinksFeature(config, logger);
}

public AssistanceFeature getReports() {
Expand Down Expand Up @@ -179,6 +182,10 @@ public MatchHistoryFeature getHistory() {
return history;
}

public ServerLinksFeature getServerLinks() {
return serverLinks;
}

public void reloadConfig(Configuration config) {
// Reload all config values here
getReports().getConfig().reload(config);
Expand All @@ -200,6 +207,7 @@ public void reloadConfig(Configuration config) {
getPolls().getConfig().reload(config);
getSquads().getConfig().reload(config);
getHistory().getConfig().reload(config);
getServerLinks().getConfig().reload(config);

// TODO: Look into maybe unregister commands for features that have been disabled
// commands#unregisterCommand
Expand All @@ -226,5 +234,6 @@ public void disable() {
if (getPolls().isEnabled()) getPolls().disable();
if (getSquads().isEnabled()) getSquads().disable();
if (getHistory().isEnabled()) getHistory().disable();
if (getServerLinks().isEnabled()) getServerLinks().disable();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package dev.pgm.community.serverlinks;

import static tc.oc.pgm.util.text.TextParser.parseComponent;
import static tc.oc.pgm.util.text.TextParser.parseEnum;
import static tc.oc.pgm.util.text.TextParser.parseUri;

import dev.pgm.community.feature.config.FeatureConfigImpl;
import dev.pgm.community.serverlinks.types.ServerLink;
import dev.pgm.community.serverlinks.types.ServerLinkBuiltinType;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.bukkit.configuration.Configuration;

public class ServerLinksConfig extends FeatureConfigImpl {
private static final String KEY = "server-links";
private static final String LINKS_KEY = "links";

private static final String LINK_BUILTIN_KEY = "builtin";
private static final String LINK_CUSTOM_TEXT_KEY = "text";
private static final String LINK_URI_KEY = "uri";

private List<ServerLink> links;

public ServerLinksConfig(Configuration config) {
super(KEY, config);
}

public List<ServerLink> getLinks() {
return links;
}

@Override
public void reload(Configuration config) {
super.reload(config);
links = config.getMapList(getKey() + "." + LINKS_KEY).stream()
.map(this::readLink)
.toList();
}

private ServerLink readLink(Map<?, ?> configData) {
String builtIn = Objects.toString(configData.get(LINK_BUILTIN_KEY), null);
String customText = Objects.toString(configData.get(LINK_CUSTOM_TEXT_KEY), null);
String uri = Objects.toString(configData.get(LINK_URI_KEY), null);

if ((builtIn == null) == (customText == null)) {
throw new IllegalStateException(
"A server link must have either built-in or custom text defined");
}

URI parsedUri = parseUri(uri);
if (!parsedUri.getScheme().equals("http") && !parsedUri.getScheme().equals("https")) {
throw new IllegalStateException("The URL " + uri + " is not a web URL");
}

return new ServerLink(
builtIn != null ? parseEnum(builtIn, ServerLinkBuiltinType.class) : null,
customText != null ? parseComponent(customText) : null,
parsedUri);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package dev.pgm.community.serverlinks;

import dev.pgm.community.feature.FeatureBase;
import dev.pgm.community.serverlinks.types.ServerLink;
import dev.pgm.community.util.Platform;
import java.util.List;
import java.util.logging.Logger;
import org.bukkit.configuration.Configuration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;

public class ServerLinksFeature extends FeatureBase {
private static final ServerLinksPlatform PLATFORM = Platform.get(ServerLinksPlatform.class);

public interface ServerLinksPlatform {
default boolean isSupported() {
return true;
}

void sendToPlayer(Player player, List<ServerLink> serverLinks);
}

public ServerLinksFeature(Configuration config, Logger logger) {
super(new ServerLinksConfig(config), logger, "Server Links");

if (getConfig().isEnabled()) {
if (!PLATFORM.isSupported()) {
logger.warning("Server links are enabled but not supported by the platform");
return;
}
enable();
}
}

@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
PLATFORM.sendToPlayer(event.getPlayer(), getServerLinksConfig().getLinks());
}

public ServerLinksConfig getServerLinksConfig() {
return (ServerLinksConfig) getConfig();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dev.pgm.community.serverlinks.types;

import java.net.URI;
import net.kyori.adventure.text.Component;

/**
* Represents a Minecraft server link.
*
* @param builtinType The built-in type of the server link, or null if it's a custom link.
* @param customText The custom text for the server link, or null if builtinType is set.
* @param uri The URI of the server link.
*/
public record ServerLink(ServerLinkBuiltinType builtinType, Component customText, URI uri) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.pgm.community.serverlinks.types;

/**
* Represents a built-in server link type that will be auto-translated by the Minecraft client and
* possibly have special functionality. Keep in sync with Paper's org.bukkit.ServerLinks.Type.
*/
public enum ServerLinkBuiltinType {
REPORT_BUG,
COMMUNITY_GUIDELINES,
SUPPORT,
STATUS,
FEEDBACK,
COMMUNITY,
WEBSITE,
FORUMS,
NEWS,
ANNOUNCEMENTS;
}
12 changes: 12 additions & 0 deletions core/src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -385,3 +385,15 @@ database:
host: "localhost:3306" # host and port
timezone: "America/Los_Angeles" # Database timezone
max-connections: 2 # Maximum simultaneous connections (does not impact SQLite)

# Server Links - Adds links to the pause menu for 1.21+ clients
# Requires ViaVersion to be installed on 1.8-based servers.
server-links:
enabled: false
links:
# A built-in server link type will be auto-translated by the client and may provide some extra functionality.
- builtin: report bug # See https://jd.papermc.io/paper/org/bukkit/ServerLinks.Type.html for a list of built-in types
uri: https://pgm.dev
# Alternatively, custom text can be provided. Custom text is mutually exclusive with built-in types.
- text: "Submit a new map"
uri: https://pgm.dev
2 changes: 1 addition & 1 deletion core/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ main: ${mainClass}
version: ${version} (git-${commitHash})
website: ${url}
author: ${author}
softdepend: [PGM, Environment]
softdepend: [PGM, Environment, ViaVersion]
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package dev.pgm.community.platform.modern.feature.serverlinks;

import static dev.pgm.community.util.Supports.Variant.PAPER;

import dev.pgm.community.serverlinks.ServerLinksFeature;
import dev.pgm.community.serverlinks.types.ServerLink;
import dev.pgm.community.serverlinks.types.ServerLinkBuiltinType;
import dev.pgm.community.util.Supports;
import java.util.List;
import org.bukkit.ServerLinks;
import org.bukkit.craftbukkit.CraftServerLinks;
import org.bukkit.entity.Player;

@Supports(PAPER)
public class ModernServerLinksPlatform implements ServerLinksFeature.ServerLinksPlatform {
@Override
public void sendToPlayer(Player player, List<ServerLink> serverLinks) {
player.sendLinks(toPlatformServerLinks(serverLinks));
}

private ServerLinks toPlatformServerLinks(List<ServerLink> links) {
ServerLinks bukkitLinks = new CraftServerLinks(new net.minecraft.server.ServerLinks(List.of()));
for (ServerLink link : links) {
if (link.builtinType() != null) {
bukkitLinks.addLink(toBukkitType(link.builtinType()), link.uri());
} else {
bukkitLinks.addLink(link.customText(), link.uri());
}
}

return bukkitLinks;
}

private ServerLinks.Type toBukkitType(ServerLinkBuiltinType type) {
return ServerLinks.Type.values()[type.ordinal()];
}
}
5 changes: 5 additions & 0 deletions platform/platform-sportpaper/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ plugins {
id("buildlogic.java-conventions")
}

repositories {
maven("https://repo.viaversion.com") // ViaVersion
}

dependencies {
implementation(project(":core"))
implementation(project(":util"))
compileOnly("app.ashcon:sportpaper:1.8.8-R0.1-SNAPSHOT")
compileOnly("com.viaversion:viaversion-api:5.0.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dev.pgm.community.platform.sportpaper.features.serverlinks;

import static dev.pgm.community.util.Supports.Variant.SPORTPAPER;

import dev.pgm.community.serverlinks.ServerLinksFeature;
import dev.pgm.community.serverlinks.types.ServerLink;
import dev.pgm.community.util.Supports;
import java.util.List;
import org.bukkit.entity.Player;

@Supports(SPORTPAPER)
public class SpServerLinksPlatform implements ServerLinksFeature.ServerLinksPlatform {
private static final boolean HAS_VIA = hasVia();

private static boolean hasVia() {
try {
Class.forName("com.viaversion.viaversion.api.Via");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}

@Override
public boolean isSupported() {
return HAS_VIA;
}

@Override
public void sendToPlayer(Player player, List<ServerLink> serverLinks) {
if (HAS_VIA) {
ViaServerLinks.sendToPlayer(player, serverLinks);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dev.pgm.community.platform.sportpaper.features.serverlinks;

import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.libs.gson.JsonParser;
import com.viaversion.viaversion.libs.mcstructs.text.utils.JsonNbtConverter;
import dev.pgm.community.serverlinks.types.ServerLink;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.bukkit.entity.Player;

public class ViaServerLinks {
private static final Protocol<?, ?, ?, ?> serverLinkProtocol = findServerLinkProtocol();

public static void sendToPlayer(Player player, List<ServerLink> serverLinks) {
if (!Via.getAPI().isInjected(player.getUniqueId())) return;
UserConnection userConnection = Via.getAPI().getConnection(player.getUniqueId());
if (userConnection != null
&& userConnection
.getProtocolInfo()
.protocolVersion()
.newerThanOrEqualTo(ProtocolVersion.v1_21)) {
PacketWrapper serverLinksPacket = createPacket(userConnection, serverLinks);
serverLinksPacket.scheduleSend(serverLinkProtocol.getClass());
}
}

private static PacketWrapper createPacket(UserConnection conn, List<ServerLink> links) {
var packetTypes = serverLinkProtocol.getPacketTypesProvider().mappedClientboundPacketTypes();
var packetType = packetTypes.get(State.PLAY).typeByName("SERVER_LINKS");
PacketWrapper packet = PacketWrapper.create(packetType, conn);
packet.write(Types.VAR_INT, links.size());
// TODO: is there a better way to do this?
for (ServerLink link : links) {
packet.write(Types.BOOLEAN, link.builtinType() != null);
if (link.builtinType() != null) {
packet.write(Types.VAR_INT, link.builtinType().ordinal());
} else {
packet.write(Types.TAG, toViaTag(link.customText()));
}
packet.write(Types.STRING, link.uri().toString());
}

return packet;
}

private static Tag toViaTag(Component component) {
return JsonNbtConverter.toNbt(
JsonParser.parseString(GsonComponentSerializer.gson().serialize(component)));
}

private static Protocol<?, ?, ?, ?> findServerLinkProtocol() {
return Via.getManager()
.getProtocolManager()
.getProtocol(/* to */ ProtocolVersion.v1_21, /* from */ ProtocolVersion.v1_20_5);
}
}