diff --git a/src/de/steamwar/lobby/Config.java b/src/de/steamwar/lobby/Config.java index e5c055c..15ad255 100644 --- a/src/de/steamwar/lobby/Config.java +++ b/src/de/steamwar/lobby/Config.java @@ -19,23 +19,30 @@ package de.steamwar.lobby; +import de.steamwar.lobby.display.Hologram; import de.steamwar.lobby.portal.Portal; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.serialization.ConfigurationSerialization; public class Config { + static { + ConfigurationSerialization.registerClass(Portal.class); + ConfigurationSerialization.registerClass(Hologram.class); + } + private final FileConfiguration yml; public Config(FileConfiguration yml) { this.yml = yml; - ConfigurationSerialization.registerClass(Portal.class); yml.getList("portals", Portal.getPortals()); + yml.getList("holograms", Hologram.getHolograms()); } public void save() { yml.set("portals", Portal.getPortals()); + yml.set("holograms", Hologram.getHolograms()); LobbySystem.getPlugin().saveConfig(); } diff --git a/src/de/steamwar/lobby/command/PortalCommand.java b/src/de/steamwar/lobby/command/PortalCommand.java index 726e19f..053a601 100644 --- a/src/de/steamwar/lobby/command/PortalCommand.java +++ b/src/de/steamwar/lobby/command/PortalCommand.java @@ -3,7 +3,6 @@ package de.steamwar.lobby.command; import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.RegionSelector; import de.steamwar.command.SWCommand; @@ -12,7 +11,6 @@ import de.steamwar.lobby.LobbySystem; import de.steamwar.lobby.portal.*; import de.steamwar.sql.SteamwarUser; import lombok.Data; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.command.CommandSender; @@ -23,8 +21,6 @@ import java.util.List; public class PortalCommand extends SWCommand { - private static final WorldEditPlugin worldEditPlugin = (WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit"); - public PortalCommand() { super("portal"); } diff --git a/src/de/steamwar/lobby/Displayable.java b/src/de/steamwar/lobby/display/Displayable.java similarity index 98% rename from src/de/steamwar/lobby/Displayable.java rename to src/de/steamwar/lobby/display/Displayable.java index f8836a2..52ab923 100644 --- a/src/de/steamwar/lobby/Displayable.java +++ b/src/de/steamwar/lobby/display/Displayable.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package de.steamwar.lobby; +package de.steamwar.lobby.display; import de.steamwar.lobby.listener.BasicListener; import org.bukkit.Bukkit; diff --git a/src/de/steamwar/lobby/display/Hologram.java b/src/de/steamwar/lobby/display/Hologram.java new file mode 100644 index 0000000..7a2e68a --- /dev/null +++ b/src/de/steamwar/lobby/display/Hologram.java @@ -0,0 +1,156 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2021 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.lobby.display; + +import com.comphenix.tinyprotocol.Reflection; +import de.steamwar.lobby.listener.Protocol; +import org.bukkit.Location; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.util.*; + +public class Hologram implements ConfigurationSerializable { + + private static final Class dataWatcherObject = Reflection.getClass("{nms}.DataWatcherObject"); + private static final Class dataWatcherRegistry = Reflection.getClass("{nms}.DataWatcherRegistry"); + private static final Class dataWatcherSerializer = Reflection.getClass("{nms}.DataWatcherSerializer"); + private static final Reflection.ConstructorInvoker dataWatcherObjectConstructor = Reflection.getConstructor(dataWatcherObject, int.class, dataWatcherSerializer); + private static Object getDataWatcherObject(int index, Class type) { + for(Field field : dataWatcherRegistry.getFields()) { + if(dataWatcherSerializer.isAssignableFrom(field.getType()) && type.equals(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0])) { + try { + return dataWatcherObjectConstructor.invoke(index, field.get(null)); + } catch (IllegalAccessException e) { + throw new SecurityException("Could not get field", e); + } + } + } + throw new SecurityException("Could not find Serializer for " + type.getName()); + } + + private static final Class spawnLivingPacket = Reflection.getClass("{nms}.PacketPlayOutSpawnEntityLiving"); + private static final Reflection.ConstructorInvoker spawnLivingPacketConstructor = Reflection.getConstructor(spawnLivingPacket); + private static final Reflection.FieldAccessor spawnLivingEntityId = Reflection.getField(spawnLivingPacket, int.class, 0); + private static final Reflection.FieldAccessor spawnLivingUUID = Reflection.getField(spawnLivingPacket, UUID.class, 0); + private static final Reflection.FieldAccessor spawnLivingEntityType = Reflection.getField(spawnLivingPacket, int.class, 1); + private static final Reflection.FieldAccessor spawnLivingEntityX = Reflection.getField(spawnLivingPacket, double.class, 0); + private static final Reflection.FieldAccessor spawnLivingEntityY = Reflection.getField(spawnLivingPacket, double.class, 1); + private static final Reflection.FieldAccessor spawnLivingEntityZ = Reflection.getField(spawnLivingPacket, double.class, 2); + + private static final Class metadataPacket = Reflection.getClass("{nms}.PacketPlayOutEntityMetadata"); + private static final Reflection.ConstructorInvoker metadataConstructor = Reflection.getConstructor(metadataPacket); + private static final Reflection.FieldAccessor metadataEntity = Reflection.getField(metadataPacket, int.class, 0); + private static final Reflection.FieldAccessor metadataMetadata = Reflection.getField(metadataPacket, List.class, 0); + private static final Class item = Reflection.getClass("{nms}.DataWatcher$Item"); + private static final Reflection.ConstructorInvoker itemConstructor = Reflection.getConstructor(item, dataWatcherObject, Object.class); + private static final Object invisibleWatcher = getDataWatcherObject(0, Byte.class); + private static final Object nameWatcher = getDataWatcherObject(2, String.class); + private static final Object nameVisibleWatcher = getDataWatcherObject(3, String.class); + private static final Object silentWatcher = getDataWatcherObject(4, Boolean.class); + private static final Object noGravityWatcher = getDataWatcherObject(5, Boolean.class); + private static final Object sizeWatcher = getDataWatcherObject(15, Byte.class); + + private static final Class destroyPacket = Reflection.getClass("{nms}.PacketPlayOutEntityDestroy"); + private static final Reflection.ConstructorInvoker destoryPacketConstructor = Reflection.getConstructor(destroyPacket); + private static final Reflection.FieldAccessor destroyIds = Reflection.getField(destroyPacket, int[].class, 0); + + private static final List holograms = new ArrayList<>(); + private static int entityIds = -1; + private static final Random random = new Random(); + + public static List getHolograms() { + return holograms; + } + + private final Displayable display; + private final int entityId; + private final Object spawnLiving; + private final Object destroy; + private final String id; + private final Location location; + private final String text; + private final boolean persistent; + + public Hologram(Map map) { + this((String) map.get("id"), (Location) map.get("location"), (String) map.get("text"), true); + } + + public Hologram(String id, Location location, String text, boolean persistent) { + this.id = id; + this.location = location; + this.text = text; + this.persistent = persistent; + entityId = entityIds--; + + spawnLiving = spawnLivingPacketConstructor.invoke(); + spawnLivingEntityId.set(spawnLiving, entityId); + spawnLivingUUID.set(spawnLiving, new UUID(random.nextLong() & -61441L | 16384L, random.nextLong() & 4611686018427387903L | -9223372036854775808L)); + spawnLivingEntityType.set(spawnLiving, 1); + spawnLivingEntityX.set(spawnLiving, location.getX()); + spawnLivingEntityY.set(spawnLiving, location.getY()); + spawnLivingEntityZ.set(spawnLiving, location.getZ()); + + destroy = destoryPacketConstructor.invoke(); + destroyIds.set(destroy, new int[]{entityId}); + + display = new Displayable(location, this::show, this::hide); + + if(persistent) + holograms.add(this); + } + + private void show(Player player) { + Protocol.getTinyProtocol().sendPacket(player, spawnLiving); + + Object packet = metadataConstructor.invoke(); + metadataEntity.set(packet, entityId); + List watchers = new ArrayList<>(); + watchers.add(itemConstructor.invoke(invisibleWatcher, (byte) 0x20)); + watchers.add(itemConstructor.invoke(nameWatcher, text)); + watchers.add(itemConstructor.invoke(nameVisibleWatcher, true)); + watchers.add(itemConstructor.invoke(silentWatcher, true)); + watchers.add(itemConstructor.invoke(noGravityWatcher, true)); + watchers.add(itemConstructor.invoke(sizeWatcher, (byte) 0x10)); + metadataMetadata.set(packet, watchers); + Protocol.getTinyProtocol().sendPacket(player, packet); + } + + private void hide(Player player) { + Protocol.getTinyProtocol().sendPacket(player, destroy); + } + + @Override + public Map serialize() { + Map map = new HashMap<>(); + map.put("id", id); + map.put("location", location); + map.put("text", text); + return map; + } + + public void delete() { + display.delete(); + if(persistent) + holograms.remove(this); + } +} diff --git a/src/de/steamwar/lobby/listener/Protocol.java b/src/de/steamwar/lobby/listener/Protocol.java new file mode 100644 index 0000000..8655cea --- /dev/null +++ b/src/de/steamwar/lobby/listener/Protocol.java @@ -0,0 +1,44 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2021 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.lobby.listener; + +import com.comphenix.tinyprotocol.TinyProtocol; +import de.steamwar.lobby.LobbySystem; +import io.netty.channel.Channel; +import org.bukkit.entity.Player; + +public class Protocol { + + private static final TinyProtocol tinyProtocol = new TinyProtocol(LobbySystem.getPlugin()) { + @Override + public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) { + return super.onPacketOutAsync(receiver, channel, packet); + } + + @Override + public Object onPacketInAsync(Player sender, Channel channel, Object packet) { + return super.onPacketInAsync(sender, channel, packet); + } + }; + + public static TinyProtocol getTinyProtocol() { + return tinyProtocol; + } +} diff --git a/src/plugin.yml b/src/plugin.yml index 76163e9..e1b8284 100644 --- a/src/plugin.yml +++ b/src/plugin.yml @@ -5,6 +5,3 @@ authors: main: de.steamwar.lobby.LobbySystem depend: [SpigotCore, WorldEdit] api-version: "1.13" - -commands: - ak: