diff --git a/CommonCore/Network/src/de/steamwar/network/packets/common/PlayerSkinRequestPacket.java b/CommonCore/Network/src/de/steamwar/network/packets/common/PlayerSkinRequestPacket.java new file mode 100644 index 00000000..4916e0f1 --- /dev/null +++ b/CommonCore/Network/src/de/steamwar/network/packets/common/PlayerSkinRequestPacket.java @@ -0,0 +1,36 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.network.packets.common; + +import de.steamwar.network.packets.NetworkPacket; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +import java.util.UUID; + +@AllArgsConstructor +@Getter +@ToString +public class PlayerSkinRequestPacket extends NetworkPacket { + + private static final long serialVersionUID = 277267302555671765L; + private UUID uuid; +} diff --git a/CommonCore/Network/src/de/steamwar/network/packets/common/PlayerSkinResponsePacket.java b/CommonCore/Network/src/de/steamwar/network/packets/common/PlayerSkinResponsePacket.java new file mode 100644 index 00000000..5c3767ca --- /dev/null +++ b/CommonCore/Network/src/de/steamwar/network/packets/common/PlayerSkinResponsePacket.java @@ -0,0 +1,38 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.network.packets.common; + +import de.steamwar.network.packets.NetworkPacket; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +import java.util.UUID; + +@AllArgsConstructor +@Getter +@ToString +public class PlayerSkinResponsePacket extends NetworkPacket { + + private static final long serialVersionUID = 5792855362547625112L; + private UUID uuid; + private String skin; + private String signature; +} diff --git a/LobbySystem/build.gradle.kts b/LobbySystem/build.gradle.kts index aa511a0c..8662545c 100644 --- a/LobbySystem/build.gradle.kts +++ b/LobbySystem/build.gradle.kts @@ -34,3 +34,12 @@ dependencies { compileOnly(libs.nms20) compileOnly(libs.worldedit15) } + +tasks.register("DevLobby20") { + group = "run" + description = "Run a 1.20 Dev Lobby" + dependsOn(":SpigotCore:shadowJar") + dependsOn(":LobbySystem:jar") + template = "Lobby20" + worldName = "Lobby" +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java index b4aaa3ee..2575a778 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java @@ -19,18 +19,22 @@ package de.steamwar.entity; -import de.steamwar.Reflection; import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import de.steamwar.Reflection; import de.steamwar.core.BountifulWrapper; import de.steamwar.core.Core; import de.steamwar.core.FlatteningWrapper; import de.steamwar.core.ProtocolWrapper; +import de.steamwar.network.NetworkSender; +import de.steamwar.network.packets.common.PlayerSkinRequestPacket; import lombok.Getter; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; +import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import java.util.function.Consumer; @@ -61,17 +65,40 @@ public class RPlayer extends REntity { private static final Object skinPartsDataWatcher = BountifulWrapper.impl.getDataWatcherObject(skinPartsIndex(), Byte.class); + private final UUID actualUUID; private final String name; public RPlayer(REntityServer server, UUID uuid, String name, Location location) { - super(server, EntityType.PLAYER, uuid, location,0); + super(server, EntityType.PLAYER, UUID.randomUUID(), location,0); + this.actualUUID = uuid; this.name = name; server.addEntity(this); } + public static final Map SKIN_DATA_PROMISES = new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 100; + } + }; + + private GameProfile getGameProfile() { + Property skinData = SKIN_DATA_PROMISES.computeIfAbsent(uuid, __ -> { + NetworkSender.sendOrQueue(new PlayerSkinRequestPacket(uuid)); + return new Property("textures", null, null); + }); + if (skinData.getValue() != null) { + GameProfile gameProfile = new GameProfile(uuid, name); + gameProfile.getProperties().put("textures", skinData); + return gameProfile; + } else { + return new GameProfile(actualUUID, name); + } + } + @Override void list(Consumer packetSink) { - packetSink.accept(ProtocolWrapper.impl.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.ADD, new GameProfile(uuid, name), GameMode.CREATIVE)); + packetSink.accept(ProtocolWrapper.impl.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.ADD, getGameProfile(), GameMode.CREATIVE)); } @Override @@ -88,7 +115,7 @@ public class RPlayer extends REntity { @Override void delist(Consumer packetSink) { - packetSink.accept(ProtocolWrapper.impl.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(uuid, name), GameMode.CREATIVE)); + packetSink.accept(ProtocolWrapper.impl.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, getGameProfile(), GameMode.CREATIVE)); } private static final Class namedSpawnPacket = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket"); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/network/CoreNetworkHandler.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/network/CoreNetworkHandler.java index bf74682a..50ee9fcb 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/network/CoreNetworkHandler.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/network/CoreNetworkHandler.java @@ -19,9 +19,12 @@ package de.steamwar.network; +import com.mojang.authlib.properties.Property; import de.steamwar.core.BountifulWrapper; +import de.steamwar.entity.RPlayer; import de.steamwar.network.handlers.InventoryHandler; import de.steamwar.network.packets.PacketHandler; +import de.steamwar.network.packets.common.PlayerSkinResponsePacket; import de.steamwar.network.packets.server.*; import de.steamwar.sql.BauweltMember; import de.steamwar.sql.SteamwarUser; @@ -68,4 +71,11 @@ public class CoreNetworkHandler extends PacketHandler { public void handleLocaleChange(LocaleInvalidationPacket packet) { SteamwarUser.invalidate(packet.getPlayerId()); } + + @Handler + public void handlePlayerSkinResponse(PlayerSkinResponsePacket packet) { + Property property = RPlayer.SKIN_DATA_PROMISES.get(packet.getUuid()); + if (property == null) return; + RPlayer.SKIN_DATA_PROMISES.put(packet.getUuid(), new Property("textures", packet.getSkin(), packet.getSignature())); + } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/network/NetworkSender.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/network/NetworkSender.java index 10a387d8..8000ff23 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/network/NetworkSender.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/network/NetworkSender.java @@ -24,8 +24,42 @@ import de.steamwar.network.packets.NetworkPacket; import lombok.SneakyThrows; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; -public class NetworkSender { +import java.util.ArrayList; +import java.util.List; + +public class NetworkSender implements Listener { + + private static List queued = new ArrayList<>(); + + static { + Bukkit.getPluginManager().registerEvents(new NetworkSender(), Core.getInstance()); + } + + private NetworkSender() { + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + if (!Bukkit.getOnlinePlayers().isEmpty()) { + return; + } + Bukkit.getScheduler().runTaskLater(Core.getInstance(), () -> { + queued.forEach(NetworkSender::send); + queued.clear(); + }, 1); + } + + public static void sendOrQueue(NetworkPacket packet) { + if (!Bukkit.getOnlinePlayers().isEmpty()) { + send(packet); + } else { + queued.add(packet); + } + } public static void send(NetworkPacket packet) { Bukkit.getOnlinePlayers().stream().findAny().ifPresent(player -> send(packet, player)); diff --git a/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java b/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java index 517d3342..841b657d 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java +++ b/VelocityCore/src/de/steamwar/velocitycore/VelocityCore.java @@ -225,7 +225,7 @@ public class VelocityCore implements ReloadablePlugin { for(PacketHandler handler : new PacketHandler[] { new EloPlayerHandler(), new EloSchemHandler(), new ExecuteCommandHandler(), new FightInfoHandler(), - new ImALobbyHandler(), new InventoryCallbackHandler(), new PrepareSchemHandler() + new ImALobbyHandler(), new InventoryCallbackHandler(), new PrepareSchemHandler(), new PlayerSkinHandler() }) handler.register(); diff --git a/VelocityCore/src/de/steamwar/velocitycore/network/handlers/PlayerSkinHandler.java b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/PlayerSkinHandler.java new file mode 100644 index 00000000..0515212e --- /dev/null +++ b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/PlayerSkinHandler.java @@ -0,0 +1,121 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.velocitycore.network.handlers; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.util.GameProfile; +import de.steamwar.network.packets.PacketHandler; +import de.steamwar.network.packets.common.PlayerSkinRequestPacket; +import de.steamwar.network.packets.common.PlayerSkinResponsePacket; +import de.steamwar.velocitycore.VelocityCore; +import de.steamwar.velocitycore.network.NetworkSender; +import de.steamwar.velocitycore.network.ServerMetaInfo; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.SneakyThrows; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +public class PlayerSkinHandler extends PacketHandler { + + private final int maxCacheSize = 1000; + + public PlayerSkinHandler() { + VelocityCore.getProxy().getEventManager().register(VelocityCore.get(), this); + } + + private Map skins = new LinkedHashMap<>() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxCacheSize; + } + }; + + @Handler + @SneakyThrows + public void handle(PlayerSkinRequestPacket packet) { + if (skins.containsKey(packet.getUuid())) { + SkinData skinData = skins.get(packet.getUuid()); + NetworkSender.send(((ServerMetaInfo) packet.getMetaInfos()).sender().getServer(), new PlayerSkinResponsePacket(packet.getUuid(), skinData.skin, skinData.signature)); + return; + } + + String url = "https://sessionserver.mojang.com/session/minecraft/profile/" + packet.getUuid().toString().replace("-", "") + "?unsigned=false"; + + HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); + connection.setReadTimeout(5000); + connection.setConnectTimeout(5000); + connection.setRequestProperty("User-Agent", "SkinFetcher"); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + return; + } + + InputStream is = connection.getInputStream(); + String json = new BufferedReader(new InputStreamReader(is)) + .lines().collect(Collectors.joining("\n")); + + JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); + JsonArray properties = obj.getAsJsonArray("properties"); + for (JsonElement propElement : properties) { + JsonObject prop = propElement.getAsJsonObject(); + if (prop.get("name").getAsString().equals("textures")) { + String skin = prop.get("value").getAsString(); + String signature = prop.get("signature").getAsString(); + skins.put(packet.getUuid(), new SkinData(skin, signature)); + NetworkSender.send(((ServerMetaInfo) packet.getMetaInfos()).sender().getServer(), new PlayerSkinResponsePacket(packet.getUuid(), skin, signature)); + return; + } + } + } + + @Subscribe + public void onPostLogin(PostLoginEvent event) { + Player player = event.getPlayer(); + GameProfile gameProfile = player.getGameProfile(); + GameProfile.Property property = gameProfile.getProperties().stream().filter(p -> p.getName().equals("textures")).findFirst().orElse(null); + if (property == null) return; + skins.put(player.getUniqueId(), new SkinData(property.getValue(), property.getSignature())); + + Set uuidSet = skins.keySet(); + VelocityCore.getProxy().getAllServers().forEach(server -> { + for (UUID uuid : uuidSet) { + NetworkSender.send(server, new PlayerSkinResponsePacket(uuid, property.getValue(), property.getSignature())); + } + }); + } + + public record SkinData(String skin, String signature) {} +}