Skip to content
Open
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
6 changes: 4 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ plugins {
group = "org.geysermc.globallinkserver"

dependencies {
paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT")
paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT")

compileOnly(libs.geyser.core)
compileOnly(libs.floodgate.api)
implementation(libs.mariadb.client)
implementation(libs.hikaricp)
implementation(libs.postgresql)
implementation(libs.bundles.fastutil)

compileOnly(libs.checker.qual)
Expand Down
12 changes: 8 additions & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
[versions]
geyser = "2.9.4-SNAPSHOT"
floodgate = "2.2.3-SNAPSHOT"
mariadb-client = "2.7.3"
hikari = "7.0.2"
postgresql = "42.7.8"
fastutil = "8.5.2"
checker-qual = "3.21.1"

indra = "3.1.2"
paperweight = "2.0.0-beta.12"
runpaper = "2.3.1"
paperweight = "2.0.0-beta.19"
runpaper = "3.0.1"

[libraries]
geyser-core = { module = "org.geysermc.geyser:core", version.ref = "geyser" }
floodgate-api = { group = "org.geysermc.floodgate", name = "api", version.ref = "floodgate" }
mariadb-client = { module = "org.mariadb.jdbc:mariadb-java-client", version.ref = "mariadb-client" }
hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikari" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }

fastutil-int-int-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int-int-maps", version.ref = "fastutil" }
fastutil-int-object-maps = { group = "com.nukkitx.fastutil", name = "fastutil-int-object-maps", version.ref = "fastutil" }
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
3 changes: 1 addition & 2 deletions gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pluginManagement {
repositories {
gradlePluginPortal()
maven("https://repo.papermc.io/repository/maven-public/")
mavenCentral()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2025 GeyserMC
* Copyright (c) 2021-2026 GeyserMC
* Licensed under the MIT license
* @link https://github.com/GeyserMC/GlobalLinkServer
*/
Expand All @@ -22,7 +22,6 @@
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
Expand All @@ -33,6 +32,7 @@
import org.bukkit.plugin.java.JavaPlugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.globallinkserver.config.ConfigReader;
import org.geysermc.globallinkserver.handler.CommandHandler;
import org.geysermc.globallinkserver.handler.JoinHandler;
Expand All @@ -43,6 +43,7 @@
import org.geysermc.globallinkserver.manager.PlayerManager;
import org.geysermc.globallinkserver.service.LinkInfoService;
import org.geysermc.globallinkserver.service.LinkLookupService;
import org.geysermc.globallinkserver.service.MappingService;
import org.geysermc.globallinkserver.util.MultiConditionSet;
import org.geysermc.globallinkserver.util.Utils;

Expand All @@ -68,11 +69,12 @@ public class GlobalLinkServer extends JavaPlugin implements Listener {
public void onEnable() {
var config = ConfigReader.readConfig(this);

var playerManager = new PlayerManager(FloodgateApi.getInstance());
var playerManager = new PlayerManager(FloodgateApi.getInstance(), GeyserImpl.getInstance());
var databaseManager = new DatabaseManager(config);
var linkManager = new LinkManager(playerManager, databaseManager);
linkLookupService = new LinkLookupService(playerManager, databaseManager);
linkInfoService = new LinkInfoService(linkLookupService, playerManager);
var mappingService = new MappingService(databaseManager);

var commandUtils = new CommandHandler(linkLookupService, linkInfoService, linkManager, playerManager, this);

Expand All @@ -83,7 +85,7 @@ public void onEnable() {

var pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(this, this);
pluginManager.registerEvents(new JoinHandler(linkLookupService, playerIdleTracker, this), this);
pluginManager.registerEvents(new JoinHandler(playerManager, linkLookupService, mappingService, playerIdleTracker, this), this);
pluginManager.registerEvents(new MoveInactivityHandler(playerIdleTracker), this);
pluginManager.registerEvents(new TeleportToSpawnHandler(config.spawn()), this);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2025 GeyserMC
* Copyright (c) 2021-2026 GeyserMC
* Licensed under the MIT license
* @link https://github.com/GeyserMC/GlobalLinkServer
*/
Expand Down Expand Up @@ -83,7 +83,8 @@ public int linkWithCode(CommandContext<CommandSourceStack> ctx) {
return Command.SINGLE_SUCCESS;
}

var completedLink = Link.fromRequest(linkRequest, player.getUniqueId(), player.getName(), isRequesterBedrock);
String correctUsername = playerManager.correctUsername(player);
Link completedLink = Link.fromRequest(linkRequest, player.getUniqueId(), correctUsername, isRequesterBedrock);

linkManager.finaliseLink(completedLink).whenComplete((result, error) -> {
if (error != null || !result) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 GeyserMC
* Copyright (c) 2025-2026 GeyserMC
* Licensed under the MIT license
* @link https://github.com/GeyserMC/GlobalLinkServer
*/
Expand All @@ -11,19 +11,33 @@
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.Plugin;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.auth.AuthData;
import org.geysermc.globallinkserver.Components;
import org.geysermc.globallinkserver.manager.PlayerManager;
import org.geysermc.globallinkserver.service.LinkLookupService;
import org.geysermc.globallinkserver.service.MappingService;
import org.geysermc.globallinkserver.util.MultiConditionSet;
import org.jspecify.annotations.NullMarked;

@NullMarked
public final class JoinHandler implements Listener {
private final PlayerManager playerManager;
private final LinkLookupService linkLookupService;
private final MappingService mappingService;
private final MultiConditionSet<UUID> playerIdleTracker;
private final Plugin plugin;

public JoinHandler(LinkLookupService linkLookupService, MultiConditionSet<UUID> playerIdleTracker, Plugin plugin) {
public JoinHandler(
PlayerManager playerManager,
LinkLookupService linkLookupService,
MappingService mappingService,
MultiConditionSet<UUID> playerIdleTracker,
Plugin plugin
) {
this.playerManager = playerManager;
this.linkLookupService = linkLookupService;
this.mappingService = mappingService;
this.playerIdleTracker = playerIdleTracker;
this.plugin = plugin;
}
Expand All @@ -42,6 +56,23 @@ public void onPlayerJoin(PlayerJoinEvent event) {
otherPlayer.hidePlayer(plugin, player);
});

GeyserSession bedrockSession = playerManager.bedrockSession(player);
if (bedrockSession != null) {
AuthData authData = bedrockSession.getAuthData();
mappingService.insertBedrockProfile(
player.getUniqueId().getLeastSignificantBits(),
authData.name(),
authData.playFabId(),
authData.issuedAt()
);
} else {
long earliestKnownAt = player.getPlayerProfile().getTextures().getTimestamp();
if (earliestKnownAt == 0) {
earliestKnownAt = System.currentTimeMillis();
}
mappingService.insertJavaProfile(player.getUniqueId(), player.getName(), earliestKnownAt);
}

playerIdleTracker.add(player.getUniqueId());

linkLookupService.lookup(player).whenComplete(($, throwable) -> {
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/org/geysermc/globallinkserver/link/Link.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 GeyserMC
* Copyright (c) 2025-2026 GeyserMC
* Licensed under the MIT license
* @link https://github.com/GeyserMC/GlobalLinkServer
*/
Expand All @@ -9,15 +9,15 @@
import org.jspecify.annotations.NullMarked;

@NullMarked
public record Link(UUID javaId, String javaUsername, long bedrockId) {
public Link(UUID javaId, String javaUsername, UUID bedrockId) {
this(javaId, javaUsername, bedrockId.getLeastSignificantBits());
public record Link(UUID javaId, String javaUsername, long bedrockId, String bedrockName) {
public Link(UUID javaId, String javaUsername, UUID bedrockId, String bedrockName) {
this(javaId, javaUsername, bedrockId.getLeastSignificantBits(), bedrockName);
}

public static Link fromRequest(LinkRequest left, UUID rightId, String rightName, boolean isLeftBedrock) {
public static Link fromRequest(LinkRequest left, UUID rightUuid, String rightUsername, boolean isLeftBedrock) {
if (isLeftBedrock) {
return new Link(rightId, rightName, left.requesterUuid());
return new Link(rightUuid, rightUsername, left.requesterUuid(), left.requesterUsername());
}
return new Link(left.requesterUuid(), left.requesterUsername(), rightId);
return new Link(left.requesterUuid(), left.requesterUsername(), rightUuid, rightUsername);
}
}
25 changes: 12 additions & 13 deletions src/main/java/org/geysermc/globallinkserver/link/LinkManager.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2025 GeyserMC
* Copyright (c) 2021-2026 GeyserMC
* Licensed under the MIT license
* @link https://github.com/GeyserMC/GlobalLinkServer
*/
Expand Down Expand Up @@ -46,7 +46,8 @@ public LinkManager(PlayerManager playerManager, DatabaseManager database) {
}

public int createTempLink(Player player) {
var linkRequest = new LinkRequest(createCode(), PENDING_LINK_TTL_MILLIS, player);
String correctUsername = playerManager.correctUsername(player);
var linkRequest = new LinkRequest(createCode(), PENDING_LINK_TTL_MILLIS, player.getUniqueId(), correctUsername);

linkRequests.put(linkRequest.code(), linkRequest);
linkRequestForPlayer.put(player.getUniqueId(), linkRequest.code());
Expand Down Expand Up @@ -98,15 +99,13 @@ public CompletableFuture<Boolean> finaliseLink(Link linkRequest) {
return CompletableFuture.supplyAsync(
() -> {
try (Connection connection = database.connection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `links` (`java_id`, `bedrock_id`, `java_name`) VALUES (?, ?, ?) "
+ "ON DUPLICATE KEY UPDATE "
+ "`java_id` = VALUES(`java_id`),"
+ "`bedrock_id` = VALUES(`bedrock_id`),"
+ "`java_name` = VALUES(`java_name`);")) {
query.setString(1, linkRequest.javaId().toString());
query.setLong(2, linkRequest.bedrockId());
query.setString(3, linkRequest.javaUsername());
try (PreparedStatement query = connection.prepareStatement("""
INSERT INTO links (xuid, java_id)
VALUES (?::xuid, ?::uuid)
ON CONFLICT (xuid) DO
UPDATE SET java_id = EXCLUDED.java_id, inserted_at = EXCLUDED.inserted_at""")) {
query.setLong(1, linkRequest.bedrockId());
query.setString(2, linkRequest.javaId().toString());
return query.executeUpdate() != 0;
}
} catch (SQLException exception) {
Expand All @@ -122,10 +121,10 @@ public CompletableFuture<Boolean> unlinkAccount(Player player) {
try (Connection connection = database.connection()) {
PreparedStatement query;
if (playerManager.isBedrockPlayer(player)) {
query = connection.prepareStatement("DELETE FROM `links` WHERE `bedrock_id` = ?;");
query = connection.prepareStatement("DELETE FROM links WHERE xuid = ?::xuid");
query.setLong(1, player.getUniqueId().getLeastSignificantBits());
} else {
query = connection.prepareStatement("DELETE FROM `links` WHERE `java_id` = ?;");
query = connection.prepareStatement("DELETE FROM links WHERE java_id = ?::uuid");
query.setString(1, player.getUniqueId().toString());
}
boolean affected = query.executeUpdate() != 0;
Expand Down
59 changes: 55 additions & 4 deletions src/main/java/org/geysermc/globallinkserver/link/LinkRequest.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,74 @@
/*
* Copyright (c) 2021-2025 GeyserMC
* Copyright (c) 2021-2026 GeyserMC
* Licensed under the MIT license
* @link https://github.com/GeyserMC/GlobalLinkServer
*/
package org.geysermc.globallinkserver.link;

import java.util.Objects;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

@NullMarked
public record LinkRequest(int code, long expiryTime, UUID requesterUuid, String requesterUsername) {
public LinkRequest(int code, long ttl, Player requester) {
this(code, System.currentTimeMillis() + ttl, requester.getUniqueId(), requester.getName());
public final class LinkRequest {
private final int code;
private final long expiryTime;
private final UUID requesterUuid;
private final String requesterUsername;

public LinkRequest(int code, long ttl, UUID requesterUuid, String requesterUsername) {
this.code = code;
this.expiryTime = System.nanoTime() + ttl;
this.requesterUuid = requesterUuid;
this.requesterUsername = requesterUsername;
}

public @Nullable Player requester() {
return Bukkit.getPlayer(requesterUuid);
}

public int code() {
return code;
}

public long expiryTime() {
return expiryTime;
}

public UUID requesterUuid() {
return requesterUuid;
}

public String requesterUsername() {
return requesterUsername;
}

@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (LinkRequest) obj;
return this.code == that.code &&
this.expiryTime == that.expiryTime &&
Objects.equals(this.requesterUuid, that.requesterUuid) &&
Objects.equals(this.requesterUsername, that.requesterUsername);
}

@Override
public int hashCode() {
return Objects.hash(code, expiryTime, requesterUuid, requesterUsername);
}

@Override
public String toString() {
return "LinkRequest[" +
"code=" + code + ", " +
"expiryTime=" + expiryTime + ", " +
"requesterUuid=" + requesterUuid + ", " +
"requesterUsername=" + requesterUsername + ']';
}

}
Loading
Loading