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/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Promise.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Promise.java new file mode 100644 index 00000000..393932f3 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Promise.java @@ -0,0 +1,47 @@ +/* + * 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.core; + +import lombok.NoArgsConstructor; + +import java.util.concurrent.atomic.AtomicBoolean; + +@NoArgsConstructor +public class Promise { + + private AtomicBoolean hasValue = new AtomicBoolean(false); + private E value; + + public void setValue(E value) { + this.value = value; + hasValue.set(true); + } + + public E getValue() { + if (hasValue.get()) { + return value; + } + + while (hasValue.get()) { + Thread.yield(); + } + return value; + } +} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java index 6205f5a6..5efdd3f9 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java @@ -19,38 +19,23 @@ package de.steamwar.entity; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; 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.core.*; +import de.steamwar.network.NetworkSender; +import de.steamwar.network.packets.common.PlayerSkinRequestPacket; import lombok.Getter; -import lombok.SneakyThrows; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; @Getter public class RPlayer extends REntity { @@ -87,54 +72,19 @@ public class RPlayer extends REntity { server.addEntity(this); } - private static final Map skinData = new LinkedHashMap() { + public static final Map> SKIN_DATA_PROMISES = new LinkedHashMap>() { @Override - protected boolean removeEldestEntry(Map.Entry eldest) { + protected boolean removeEldestEntry(Map.Entry> eldest) { return size() > 100; } }; - @SneakyThrows - public static Property fetchSkinData(UUID uuid) { - if (skinData.containsKey(uuid)) { - return skinData.get(uuid); - } - - String url = "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid.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 null; - } - - 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")) { - Property property = new Property( - prop.get("name").getAsString(), - prop.get("value").getAsString(), - prop.get("signature").getAsString() - ); - skinData.put(uuid, property); - return property; - } - } - - throw new IOException("Failed to fetch skin profile"); - } - private GameProfile getGameProfile() { - Property property = fetchSkinData(actualUUID); + Property property = SKIN_DATA_PROMISES.computeIfAbsent(uuid, __ -> { + Promise future = new Promise<>(); + NetworkSender.sendOrQueue(new PlayerSkinRequestPacket(uuid)); + return future; + }).getValue(); if (property != null) { GameProfile gameProfile = new GameProfile(uuid, name); gameProfile.getProperties().put("textures", property); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/network/CoreNetworkHandler.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/network/CoreNetworkHandler.java index bf74682a..407e9e41 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/network/CoreNetworkHandler.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/network/CoreNetworkHandler.java @@ -19,9 +19,13 @@ package de.steamwar.network; +import com.mojang.authlib.properties.Property; import de.steamwar.core.BountifulWrapper; +import de.steamwar.core.Promise; +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 +72,11 @@ public class CoreNetworkHandler extends PacketHandler { public void handleLocaleChange(LocaleInvalidationPacket packet) { SteamwarUser.invalidate(packet.getPlayerId()); } + + @Handler + public void handlePlayerSkinResponse(PlayerSkinResponsePacket packet) { + Promise propertyPromise = RPlayer.SKIN_DATA_PROMISES.get(packet.getUuid()); + if (propertyPromise == null) return; + propertyPromise.setValue(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..2dda8b92 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/network/NetworkSender.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/network/NetworkSender.java @@ -24,8 +24,49 @@ 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; +import org.bukkit.event.player.PlayerQuitEvent; -public class NetworkSender { +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public class NetworkSender implements Listener { + + private static AtomicInteger numberOfPlayers = new AtomicInteger(0); + private static List queued = new ArrayList<>(); + + static { + Bukkit.getPluginManager().registerEvents(new NetworkSender(), Core.getInstance()); + } + + private NetworkSender() { + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + numberOfPlayers.incrementAndGet(); + if (numberOfPlayers.get() > 1) return; + Bukkit.getScheduler().runTaskLater(Core.getInstance(), () -> { + queued.forEach(NetworkSender::send); + queued.clear(); + }, 1); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + numberOfPlayers.decrementAndGet(); + } + + public static void sendOrQueue(NetworkPacket packet) { + if (numberOfPlayers.get() > 0) { + 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..0baf15ba --- /dev/null +++ b/VelocityCore/src/de/steamwar/velocitycore/network/handlers/PlayerSkinHandler.java @@ -0,0 +1,125 @@ +/* + * 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.persistent.Storage; +import de.steamwar.velocitycore.VelocityCore; +import de.steamwar.velocitycore.network.NetworkSender; +import de.steamwar.velocitycore.network.ServerMetaInfo; +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; + } + }; + private Map signatures = new LinkedHashMap<>() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxCacheSize; + } + }; + + @Handler + @SneakyThrows + public void handle(PlayerSkinRequestPacket packet) { + if (skins.containsKey(packet.getUuid()) && signatures.containsKey(packet.getUuid())) { + NetworkSender.send(((ServerMetaInfo) packet.getMetaInfos()).sender().getServer(), new PlayerSkinResponsePacket(packet.getUuid(), skins.get(packet.getUuid()), signatures.get(packet.getUuid()))); + 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(), skin); + signatures.put(packet.getUuid(), 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(), property.getValue()); + signatures.put(player.getUniqueId(), property.getSignature()); + + Set uuidSet = skins.keySet(); + VelocityCore.getProxy().getAllServers().forEach(server -> { + for (UUID uuid : uuidSet) { + NetworkSender.send(server, new PlayerSkinResponsePacket(uuid, skins.get(uuid), signatures.get(uuid))); + } + }); + } +}