diff --git a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java index b7a678aa6..23971305d 100644 --- a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java +++ b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.api.connection; import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.connection.Connection; @@ -35,8 +36,11 @@ import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.entity.EntityData; import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; +import org.geysermc.geyser.api.skin.SkinData; +import java.util.NoSuchElementException; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; /** @@ -118,6 +122,50 @@ public interface GeyserConnection extends Connection, CommandSource { */ void sendCommand(String command); + /** + * Gets the hostname or ip address the player used to join this Geyser instance. + * Example: + * + * + * @throws NoSuchElementException if called before the session is fully initialized + * @return the ip address or hostname string the player used to join + * @since 2.8.3 + */ + @NonNull + String joinAddress(); + + /** + * Gets the port the player used to join this Geyser instance. + * Example: + * + * + * @throws NoSuchElementException if called before the session is fully initialized + * @return the port the player used to join + * @since 2.8.3 + */ + @Positive + int joinPort(); + + /** + * Applies a skin to a player seen by this Geyser connection. + * If the uuid matches the {@link GeyserConnection#javaUuid()}, this + * will update the skin of this Geyser connection. + * If the player uuid provided is not known to this connection, this method + * will silently return. + * + * @param player which player this skin should be applied to + * @param skinData the skin data to apply + * @since 2.8.3 + */ + void sendSkin(@NonNull UUID player, @NonNull SkinData skinData); + /** * @param javaId the Java entity ID to look up. * @return a {@link GeyserEntity} if present in this connection's entity tracker. diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 1c5c14c18..cab6964b8 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -46,6 +46,7 @@ import net.raphimc.minecraftauth.step.java.StepMCProfile; import net.raphimc.minecraftauth.step.java.StepMCToken; import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession; import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.index.qual.Positive; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -120,6 +121,7 @@ import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; import org.geysermc.geyser.api.event.bedrock.SessionDisconnectEvent; import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent; import org.geysermc.geyser.api.network.RemoteServer; +import org.geysermc.geyser.api.skin.SkinData; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; @@ -132,6 +134,7 @@ import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.Tickable; +import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.erosion.AbstractGeyserboundPacketHandler; @@ -149,7 +152,6 @@ import org.geysermc.geyser.inventory.recipe.GeyserSmithingRecipe; import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.BlockItem; -import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.physics.CollisionManager; @@ -184,6 +186,7 @@ import org.geysermc.geyser.session.cache.waypoint.WaypointCache; import org.geysermc.geyser.session.dialog.BuiltInDialog; import org.geysermc.geyser.session.dialog.Dialog; import org.geysermc.geyser.session.dialog.DialogManager; +import org.geysermc.geyser.skin.SkinManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; @@ -234,6 +237,8 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.UUID; @@ -1577,6 +1582,33 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { this.sendCommandPacket(command); } + @Override + public @NonNull String joinAddress() { + String combined = Optional.ofNullable(clientData).orElseThrow().getServerAddress(); + int index = combined.lastIndexOf(":"); + return combined.substring(0, index); + } + + @Override + public @Positive int joinPort() { + String combined = Optional.ofNullable(clientData).orElseThrow().getServerAddress(); + int index = combined.lastIndexOf(":"); + return Integer.parseInt(combined.substring(index + 1)); + } + + @Override + public void sendSkin(@NonNull UUID player, @NonNull SkinData skinData) { + Objects.requireNonNull(player, "player uuid must not be null!"); + Objects.requireNonNull(skinData, "skinData must not be null!"); + + PlayerEntity entity = this.entityCache.getPlayerEntity(player); + if (entity == null) { + return; + } + + SkinManager.sendSkinPacket(this, entity, skinData); + } + @Override public void openPauseScreenAdditions() { List additions = tagCache.get(DialogTag.PAUSE_SCREEN_ADDITIONS); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSessionAdapter.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSessionAdapter.java index a319e8b79..73f8b3989 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSessionAdapter.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSessionAdapter.java @@ -111,7 +111,7 @@ public class GeyserSessionAdapter extends SessionAdapter { String address; if (geyser.getConfig().getRemote().isForwardHost()) { - address = clientData.getServerAddress().split(":")[0]; + address = session.joinAddress(); } else { address = intentionPacket.getHostname(); } diff --git a/gradle.properties b/gradle.properties index 0693924f2..9f2693389 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,5 +8,5 @@ org.gradle.vfs.watch=false group=org.geysermc id=geyser -version=2.8.2-SNAPSHOT +version=2.8.3-SNAPSHOT description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.